@platforma-sdk/model 1.57.2 → 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.
- package/dist/block_migrations.cjs +60 -77
- package/dist/block_migrations.cjs.map +1 -1
- package/dist/block_migrations.d.ts +35 -32
- package/dist/block_migrations.d.ts.map +1 -1
- package/dist/block_migrations.js +60 -78
- package/dist/block_migrations.js.map +1 -1
- package/dist/block_model.cjs +18 -14
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts +5 -5
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +18 -14
- package/dist/block_model.js.map +1 -1
- package/dist/block_model_legacy.cjs +1 -0
- package/dist/block_model_legacy.cjs.map +1 -1
- package/dist/block_model_legacy.d.ts.map +1 -1
- package/dist/block_model_legacy.js +1 -0
- package/dist/block_model_legacy.js.map +1 -1
- package/dist/block_storage.cjs +24 -20
- package/dist/block_storage.cjs.map +1 -1
- package/dist/block_storage.d.ts +20 -16
- package/dist/block_storage.d.ts.map +1 -1
- package/dist/block_storage.js +24 -20
- package/dist/block_storage.js.map +1 -1
- package/dist/block_storage_callbacks.cjs +4 -3
- package/dist/block_storage_callbacks.cjs.map +1 -1
- package/dist/block_storage_callbacks.d.ts +7 -5
- package/dist/block_storage_callbacks.d.ts.map +1 -1
- package/dist/block_storage_callbacks.js +4 -3
- package/dist/block_storage_callbacks.js.map +1 -1
- package/dist/components/PFrameForGraphs.cjs +0 -117
- package/dist/components/PFrameForGraphs.cjs.map +1 -1
- package/dist/components/PFrameForGraphs.d.ts +3 -5
- package/dist/components/PFrameForGraphs.d.ts.map +1 -1
- package/dist/components/PFrameForGraphs.js +2 -117
- package/dist/components/PFrameForGraphs.js.map +1 -1
- package/dist/index.cjs +8 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/dist/pframe_utils/axes.cjs +131 -0
- package/dist/pframe_utils/axes.cjs.map +1 -0
- package/dist/pframe_utils/axes.d.ts +15 -0
- package/dist/pframe_utils/axes.d.ts.map +1 -0
- package/dist/pframe_utils/axes.js +128 -0
- package/dist/pframe_utils/axes.js.map +1 -0
- package/dist/pframe_utils/columns.cjs +4 -8
- package/dist/pframe_utils/columns.cjs.map +1 -1
- package/dist/pframe_utils/columns.js +1 -5
- package/dist/pframe_utils/columns.js.map +1 -1
- package/dist/pframe_utils/index.cjs +0 -3
- package/dist/pframe_utils/index.cjs.map +1 -1
- package/dist/pframe_utils/index.js +0 -3
- package/dist/pframe_utils/index.js.map +1 -1
- package/dist/platforma.d.ts +12 -2
- package/dist/platforma.d.ts.map +1 -1
- package/dist/plugin_handle.cjs +29 -0
- package/dist/plugin_handle.cjs.map +1 -0
- package/dist/plugin_handle.d.ts +51 -0
- package/dist/plugin_handle.d.ts.map +1 -0
- package/dist/plugin_handle.js +25 -0
- package/dist/plugin_handle.js.map +1 -0
- package/dist/plugin_model.cjs +29 -29
- package/dist/plugin_model.cjs.map +1 -1
- package/dist/plugin_model.d.ts +43 -35
- package/dist/plugin_model.d.ts.map +1 -1
- package/dist/plugin_model.js +29 -29
- package/dist/plugin_model.js.map +1 -1
- package/dist/render/api.cjs +9 -5
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +11 -5
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +9 -5
- package/dist/render/api.js.map +1 -1
- package/package.json +6 -6
- package/src/block_migrations.test.ts +109 -12
- package/src/block_migrations.ts +63 -87
- package/src/block_model.ts +34 -20
- package/src/block_model_legacy.ts +1 -0
- package/src/block_storage.test.ts +11 -10
- package/src/block_storage.ts +40 -32
- package/src/block_storage_callbacks.ts +12 -10
- package/src/components/PFrameForGraphs.ts +4 -167
- package/src/index.ts +24 -2
- package/src/pframe_utils/axes.ts +175 -0
- package/src/pframe_utils/columns.ts +2 -2
- package/src/platforma.ts +17 -2
- package/src/plugin_handle.ts +85 -0
- package/src/plugin_model.test.ts +2 -2
- package/src/plugin_model.ts +120 -58
- package/src/render/api.ts +21 -11
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var block_storage = require('./block_storage.cjs');
|
|
4
|
+
|
|
3
5
|
/** Create a DataVersioned wrapper with correct shape */
|
|
4
6
|
function makeDataVersioned(version, data) {
|
|
5
7
|
return { version, data };
|
|
6
8
|
}
|
|
9
|
+
/** Thrown when a migration step fails. */
|
|
10
|
+
class DataMigrationError extends Error {
|
|
11
|
+
name = "DataMigrationError";
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
7
16
|
/** Thrown by recover() to signal unrecoverable data. */
|
|
8
17
|
class DataUnrecoverableError extends Error {
|
|
9
18
|
name = "DataUnrecoverableError";
|
|
@@ -93,19 +102,16 @@ class MigrationChainBase {
|
|
|
93
102
|
class DataModelMigrationChainWithRecover extends MigrationChainBase {
|
|
94
103
|
recoverFn;
|
|
95
104
|
recoverFromIndex;
|
|
96
|
-
upgradeLegacyFn;
|
|
97
105
|
/** @internal */
|
|
98
106
|
constructor(state) {
|
|
99
107
|
super(state);
|
|
100
108
|
this.recoverFn = state.recoverFn;
|
|
101
109
|
this.recoverFromIndex = state.recoverFromIndex;
|
|
102
|
-
this.upgradeLegacyFn = state.upgradeLegacyFn;
|
|
103
110
|
}
|
|
104
111
|
recoverState() {
|
|
105
112
|
return {
|
|
106
113
|
recoverFn: this.recoverFn,
|
|
107
114
|
recoverFromIndex: this.recoverFromIndex,
|
|
108
|
-
upgradeLegacyFn: this.upgradeLegacyFn,
|
|
109
115
|
};
|
|
110
116
|
}
|
|
111
117
|
/**
|
|
@@ -119,7 +125,6 @@ class DataModelMigrationChainWithRecover extends MigrationChainBase {
|
|
|
119
125
|
steps,
|
|
120
126
|
recoverFn: this.recoverFn,
|
|
121
127
|
recoverFromIndex: this.recoverFromIndex,
|
|
122
|
-
upgradeLegacyFn: this.upgradeLegacyFn,
|
|
123
128
|
});
|
|
124
129
|
}
|
|
125
130
|
}
|
|
@@ -160,10 +165,9 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
160
165
|
* steps added after recover() will then run on the recovered data.
|
|
161
166
|
*
|
|
162
167
|
* Can only be called once — the returned chain has no recover() method.
|
|
163
|
-
* Mutually exclusive with upgradeLegacy().
|
|
164
168
|
*
|
|
165
169
|
* @param fn - Recovery function returning Current (the type at this chain position)
|
|
166
|
-
* @returns Builder with migrate() and init() but without recover()
|
|
170
|
+
* @returns Builder with migrate() and init() but without recover()
|
|
167
171
|
*
|
|
168
172
|
* @example
|
|
169
173
|
* // Recover between migrations — recovered data goes through v3 migration
|
|
@@ -184,18 +188,27 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
184
188
|
recoverFromIndex: this.migrationSteps.length,
|
|
185
189
|
});
|
|
186
190
|
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Initial migration chain returned by `.from()`.
|
|
194
|
+
* Extends DataModelMigrationChain with `upgradeLegacy()` — available only before
|
|
195
|
+
* any `.migrate()` calls, since legacy data always arrives at the initial version.
|
|
196
|
+
*
|
|
197
|
+
* @typeParam Current - Data type at the initial version
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
class DataModelInitialChain extends DataModelMigrationChain {
|
|
187
201
|
/**
|
|
188
202
|
* Handle legacy V1 model state ({ args, uiState }) when upgrading a block from
|
|
189
203
|
* BlockModel V1 to BlockModelV3.
|
|
190
204
|
*
|
|
191
|
-
* When a V1 block is upgraded, its stored state `{ args, uiState }`
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
205
|
+
* When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized
|
|
206
|
+
* to the internal default version. This method inserts a migration step from that
|
|
207
|
+
* internal version to the version specified in `.from()`, using the provided typed
|
|
208
|
+
* callback to transform the legacy shape. Non-legacy data passes through unchanged.
|
|
195
209
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* `upgradeLegacy()` will run on the transformed result.
|
|
210
|
+
* Must be called right after `.from()` — not available after `.migrate()` calls.
|
|
211
|
+
* Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.
|
|
199
212
|
*
|
|
200
213
|
* Can only be called once — the returned chain has no upgradeLegacy() method.
|
|
201
214
|
* Mutually exclusive with recover().
|
|
@@ -211,7 +224,7 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
211
224
|
* type BlockData = { inputFile: string; threshold: number; selectedTab: string };
|
|
212
225
|
*
|
|
213
226
|
* const dataModel = new DataModelBuilder()
|
|
214
|
-
* .from<BlockData>(
|
|
227
|
+
* .from<BlockData>("v1")
|
|
215
228
|
* .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
|
|
216
229
|
* inputFile: args.inputFile,
|
|
217
230
|
* threshold: args.threshold,
|
|
@@ -226,10 +239,17 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
226
239
|
}
|
|
227
240
|
return data;
|
|
228
241
|
};
|
|
242
|
+
// Insert DATA_MODEL_LEGACY_VERSION as the true first version
|
|
243
|
+
// with a migration step that transforms legacy data to the user's initial version.
|
|
244
|
+
const initialVersion = this.versionChain[0];
|
|
245
|
+
const step = {
|
|
246
|
+
fromVersion: block_storage.DATA_MODEL_LEGACY_VERSION,
|
|
247
|
+
toVersion: initialVersion,
|
|
248
|
+
migrate: wrappedFn,
|
|
249
|
+
};
|
|
229
250
|
return new DataModelMigrationChainWithRecover({
|
|
230
|
-
versionChain: this.versionChain,
|
|
231
|
-
steps: this.migrationSteps,
|
|
232
|
-
upgradeLegacyFn: wrappedFn,
|
|
251
|
+
versionChain: [block_storage.DATA_MODEL_LEGACY_VERSION, ...this.versionChain],
|
|
252
|
+
steps: [step, ...this.migrationSteps],
|
|
233
253
|
});
|
|
234
254
|
}
|
|
235
255
|
}
|
|
@@ -239,13 +259,13 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
239
259
|
* @example
|
|
240
260
|
* // Simple (no migrations):
|
|
241
261
|
* const dataModel = new DataModelBuilder()
|
|
242
|
-
* .from<BlockData>(
|
|
262
|
+
* .from<BlockData>("v1")
|
|
243
263
|
* .init(() => ({ numbers: [] }));
|
|
244
264
|
*
|
|
245
265
|
* @example
|
|
246
266
|
* // With migrations:
|
|
247
267
|
* const dataModel = new DataModelBuilder()
|
|
248
|
-
* .from<BlockDataV1>(
|
|
268
|
+
* .from<BlockDataV1>("v1")
|
|
249
269
|
* .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }))
|
|
250
270
|
* .migrate<BlockDataV3>("v3", (v2) => ({ ...v2, description: '' }))
|
|
251
271
|
* .init(() => ({ numbers: [], labels: [], description: '' }));
|
|
@@ -253,7 +273,7 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
253
273
|
* @example
|
|
254
274
|
* // With recover() between migrations — recovered data goes through remaining migrations:
|
|
255
275
|
* const dataModelChain = new DataModelBuilder()
|
|
256
|
-
* .from<BlockDataV1>(
|
|
276
|
+
* .from<BlockDataV1>("v1")
|
|
257
277
|
* .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }));
|
|
258
278
|
*
|
|
259
279
|
* // recover() placed before the v3 migration: recovered data goes through v3
|
|
@@ -272,7 +292,7 @@ class DataModelMigrationChain extends MigrationChainBase {
|
|
|
272
292
|
* type BlockData = { inputFile: string; selectedTab: string };
|
|
273
293
|
*
|
|
274
294
|
* const dataModel = new DataModelBuilder()
|
|
275
|
-
* .from<BlockData>(
|
|
295
|
+
* .from<BlockData>("v1")
|
|
276
296
|
* .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
|
|
277
297
|
* inputFile: args.inputFile,
|
|
278
298
|
* selectedTab: uiState.selectedTab,
|
|
@@ -284,11 +304,11 @@ class DataModelBuilder {
|
|
|
284
304
|
* Start the migration chain with the given initial data type and version key.
|
|
285
305
|
*
|
|
286
306
|
* @typeParam T - Data type for the initial version
|
|
287
|
-
* @param initialVersion - Version key string (e.g.
|
|
307
|
+
* @param initialVersion - Version key string (e.g. "v1")
|
|
288
308
|
* @returns Migration chain builder
|
|
289
309
|
*/
|
|
290
310
|
from(initialVersion) {
|
|
291
|
-
return new
|
|
311
|
+
return new DataModelInitialChain({ versionChain: [initialVersion] });
|
|
292
312
|
}
|
|
293
313
|
}
|
|
294
314
|
/**
|
|
@@ -301,7 +321,7 @@ class DataModelBuilder {
|
|
|
301
321
|
* // With recover() between migrations:
|
|
302
322
|
* // Recovered data (V2) goes through the v2→v3 migration automatically.
|
|
303
323
|
* const dataModel = new DataModelBuilder()
|
|
304
|
-
* .from<V1>(
|
|
324
|
+
* .from<V1>("v1")
|
|
305
325
|
* .migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
|
|
306
326
|
* .recover((version, data) => {
|
|
307
327
|
* if (version === "legacy") return transformLegacy(data); // returns V2
|
|
@@ -319,9 +339,7 @@ class DataModel {
|
|
|
319
339
|
initialDataFn;
|
|
320
340
|
recoverFn;
|
|
321
341
|
recoverFromIndex;
|
|
322
|
-
|
|
323
|
-
upgradeLegacyFn;
|
|
324
|
-
constructor({ versionChain, steps, initialDataFn, recoverFn = defaultRecover, recoverFromIndex, upgradeLegacyFn, }) {
|
|
342
|
+
constructor({ versionChain, steps, initialDataFn, recoverFn = defaultRecover, recoverFromIndex, }) {
|
|
325
343
|
if (versionChain.length === 0) {
|
|
326
344
|
throw new Error("DataModel requires at least one version key");
|
|
327
345
|
}
|
|
@@ -331,7 +349,6 @@ class DataModel {
|
|
|
331
349
|
this.initialDataFn = initialDataFn;
|
|
332
350
|
this.recoverFn = recoverFn;
|
|
333
351
|
this.recoverFromIndex = recoverFromIndex ?? steps.length;
|
|
334
|
-
this.upgradeLegacyFn = upgradeLegacyFn;
|
|
335
352
|
}
|
|
336
353
|
/**
|
|
337
354
|
* Internal method for creating DataModel from builder.
|
|
@@ -362,33 +379,12 @@ class DataModel {
|
|
|
362
379
|
}
|
|
363
380
|
recoverFrom(data, version) {
|
|
364
381
|
// Step 1: call the recover function to get data at the recover point
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
currentData = this.recoverFn(version, data);
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
if (isDataUnrecoverableError(error)) {
|
|
371
|
-
return { ...this.getDefaultData(), warning: error.message };
|
|
372
|
-
}
|
|
373
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
374
|
-
return {
|
|
375
|
-
...this.getDefaultData(),
|
|
376
|
-
warning: `Recover failed for version '${version}': ${errorMessage}`,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
382
|
+
// Let errors (including DataUnrecoverableError) propagate to the caller.
|
|
383
|
+
let currentData = this.recoverFn(version, data);
|
|
379
384
|
// Step 2: run any migrations that were added after recover() in the chain
|
|
380
385
|
for (let i = this.recoverFromIndex; i < this.steps.length; i++) {
|
|
381
386
|
const step = this.steps[i];
|
|
382
|
-
|
|
383
|
-
currentData = step.migrate(currentData);
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
387
|
-
return {
|
|
388
|
-
...this.getDefaultData(),
|
|
389
|
-
warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
387
|
+
currentData = step.migrate(currentData);
|
|
392
388
|
}
|
|
393
389
|
return { version: this.latestVersion, data: currentData };
|
|
394
390
|
}
|
|
@@ -396,11 +392,12 @@ class DataModel {
|
|
|
396
392
|
* Migrate versioned data from any version to the latest.
|
|
397
393
|
*
|
|
398
394
|
* - If version is in chain, applies needed migrations (O(1) lookup)
|
|
399
|
-
* - If version is unknown,
|
|
400
|
-
* - If migration
|
|
395
|
+
* - If version is unknown, attempts recovery; falls back to initial data
|
|
396
|
+
* - If a migration step fails, throws so the caller can preserve original data
|
|
401
397
|
*
|
|
402
398
|
* @param versioned - Data with version tag
|
|
403
|
-
* @returns
|
|
399
|
+
* @returns Migrated data at the latest version
|
|
400
|
+
* @throws If a migration step from a known version fails
|
|
404
401
|
*/
|
|
405
402
|
migrate(versioned) {
|
|
406
403
|
const { version: fromVersion, data } = versioned;
|
|
@@ -409,39 +406,25 @@ class DataModel {
|
|
|
409
406
|
}
|
|
410
407
|
const startIndex = this.stepsByFromVersion.get(fromVersion);
|
|
411
408
|
if (startIndex === undefined) {
|
|
412
|
-
return this.recoverFrom(data, fromVersion);
|
|
413
|
-
}
|
|
414
|
-
let currentData = data;
|
|
415
|
-
// Legacy V1 upgrade: detect and transform { args, uiState } at the initial version
|
|
416
|
-
if (startIndex === 0 && this.upgradeLegacyFn) {
|
|
417
409
|
try {
|
|
418
|
-
|
|
410
|
+
return this.recoverFrom(data, fromVersion);
|
|
419
411
|
}
|
|
420
|
-
catch
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
warning: `Legacy upgrade failed: ${errorMessage}`,
|
|
425
|
-
};
|
|
412
|
+
catch {
|
|
413
|
+
// Recovery failed (unknown version, recover fn threw, or post-recover
|
|
414
|
+
// migration failed) — reset to initial data rather than blocking the update.
|
|
415
|
+
return this.getDefaultData();
|
|
426
416
|
}
|
|
427
417
|
}
|
|
418
|
+
let currentData = data;
|
|
428
419
|
for (let i = startIndex; i < this.steps.length; i++) {
|
|
429
420
|
const step = this.steps[i];
|
|
430
|
-
|
|
431
|
-
currentData = step.migrate(currentData);
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
435
|
-
return {
|
|
436
|
-
...this.getDefaultData(),
|
|
437
|
-
warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
421
|
+
currentData = step.migrate(currentData);
|
|
440
422
|
}
|
|
441
423
|
return { version: this.latestVersion, data: currentData };
|
|
442
424
|
}
|
|
443
425
|
}
|
|
444
426
|
|
|
427
|
+
exports.DataMigrationError = DataMigrationError;
|
|
445
428
|
exports.DataModel = DataModel;
|
|
446
429
|
exports.DataModelBuilder = DataModelBuilder;
|
|
447
430
|
exports.DataUnrecoverableError = DataUnrecoverableError;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_migrations.cjs","sources":["../src/block_migrations.ts"],"sourcesContent":["export type DataVersionKey = string;\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/** 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/** Legacy V1 model state shape: { args, uiState } */\nexport type LegacyV1State<Args, UiState> = { args: Args; uiState: UiState };\n\n/** Internal state passed from builder to DataModel */\ntype BuilderState<S> = {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n initialDataFn: () => S;\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n /** Index of the first step to run after recovery. Equals the number of steps\n * present at the time recover() was called. */\n recoverFromIndex?: number;\n /** Transforms legacy V1 model data ({ args, uiState }) at the initial version. */\n upgradeLegacyFn?: (data: unknown) => unknown;\n};\n\ntype RecoverState = {\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n upgradeLegacyFn?: (data: unknown) => unknown;\n};\n\n/**\n * Abstract base for both migration chain types.\n * Holds shared state, buildStep() helper, and init().\n * migrate() cannot be shared due to a TypeScript limitation: when the base class\n * migrate() return type is abstract, subclasses cannot narrow it without losing type safety.\n * Each subclass therefore owns its migrate() with the correct concrete return type.\n *\n * @internal\n */\nabstract class MigrationChainBase<Current> {\n protected readonly versionChain: DataVersionKey[];\n protected readonly migrationSteps: MigrationStep[];\n\n protected constructor(state: { versionChain: DataVersionKey[]; steps: MigrationStep[] }) {\n this.versionChain = state.versionChain;\n this.migrationSteps = state.steps;\n }\n\n /** Appends a migration step and returns the new versionChain and steps arrays. */\n protected buildStep<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): { versionChain: DataVersionKey[]; steps: MigrationStep[] } {\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 {\n versionChain: [...this.versionChain, nextVersion],\n steps: [...this.migrationSteps, step],\n };\n }\n\n /** Returns recover-specific fields for DataModel construction. Overridden by WithRecover. */\n protected recoverState(): RecoverState {\n return {};\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * @param initialData - Factory function returning the initial state\n * @returns Finalized DataModel instance\n */\n init(initialData: DataCreateFn<Current>): DataModel<Current> {\n return DataModel[FROM_BUILDER]<Current>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n initialDataFn: initialData,\n ...this.recoverState(),\n });\n }\n}\n\n/**\n * Migration chain after recover() or upgradeLegacy() has been called.\n * Further migrate() calls are allowed; recover() and upgradeLegacy() are not\n * (enforced by type — no such methods on this class).\n *\n * @typeParam Current - Data type at the current point in the chain\n * @internal\n */\nclass DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Current> {\n private readonly recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex?: number;\n private readonly upgradeLegacyFn?: (data: unknown) => unknown;\n\n /** @internal */\n constructor(state: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n upgradeLegacyFn?: (data: unknown) => unknown;\n }) {\n super(state);\n this.recoverFn = state.recoverFn;\n this.recoverFromIndex = state.recoverFromIndex;\n this.upgradeLegacyFn = state.upgradeLegacyFn;\n }\n\n protected override recoverState(): RecoverState {\n return {\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n upgradeLegacyFn: this.upgradeLegacyFn,\n };\n }\n\n /**\n * Add a migration step. Same semantics as on the base chain.\n * recover() and upgradeLegacy() are not available — one has already been called.\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChainWithRecover<Next> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChainWithRecover<Next>({\n versionChain,\n steps,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n upgradeLegacyFn: this.upgradeLegacyFn,\n });\n }\n}\n\n/**\n * Migration chain builder.\n * Each migrate() call advances the current data type. recover() can be called once\n * at any point — it removes itself from the returned chain so it cannot be called again.\n * Duplicate version keys throw at runtime.\n *\n * @typeParam Current - Data type at the current point in the migration chain\n * @internal\n */\nclass DataModelMigrationChain<Current> extends MigrationChainBase<Current> {\n /** @internal */\n constructor({\n versionChain,\n steps = [],\n }: {\n versionChain: DataVersionKey[];\n steps?: MigrationStep[];\n }) {\n super({ versionChain, steps });\n }\n\n /**\n * Add a migration step transforming data from the current version to the next.\n *\n * @typeParam Next - Data type of the next version\n * @param nextVersion - Version key to migrate to (must be unique in the chain)\n * @param fn - Migration function\n * @returns Builder with the next version as current\n *\n * @example\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChain<Next> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChain<Next>({ versionChain, steps });\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 must return data of the type at this point in the chain (Current). Any migrate()\n * steps added after recover() will then run on the recovered data.\n *\n * Can only be called once — the returned chain has no recover() method.\n * Mutually exclusive with upgradeLegacy().\n *\n * @param fn - Recovery function returning Current (the type at this chain position)\n * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()\n *\n * @example\n * // Recover between migrations — recovered data goes through v3 migration\n * new DataModelBuilder<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === 'legacy') return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\n recover(fn: DataRecoverFn<Current>): DataModelMigrationChainWithRecover<Current> {\n return new DataModelMigrationChainWithRecover<Current>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n recoverFn: fn as (version: DataVersionKey, data: unknown) => unknown,\n recoverFromIndex: this.migrationSteps.length,\n });\n }\n\n /**\n * Handle legacy V1 model state ({ args, uiState }) when upgrading a block from\n * BlockModel V1 to BlockModelV3.\n *\n * When a V1 block is upgraded, its stored state `{ args, uiState }` arrives at the\n * initial version (DATA_MODEL_DEFAULT_VERSION) in the migration chain. This method\n * detects the legacy shape and transforms it to the current chain type using the\n * provided typed callback. Non-legacy data passes through unchanged.\n *\n * Should be called right after `.from()` (before any `.migrate()` calls), since legacy\n * data always arrives at the initial version. Any `.migrate()` steps added after\n * `upgradeLegacy()` will run on the transformed result.\n *\n * Can only be called once — the returned chain has no upgradeLegacy() method.\n * Mutually exclusive with recover().\n *\n * @typeParam Args - Type of the legacy block args\n * @typeParam UiState - Type of the legacy block uiState\n * @param fn - Typed transform from { args, uiState } to Current\n * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()\n *\n * @example\n * type OldArgs = { inputFile: string; threshold: number };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; threshold: number; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * threshold: args.threshold,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', threshold: 0, selectedTab: 'main' }));\n */\n upgradeLegacy<Args, UiState = unknown>(\n fn: (legacy: LegacyV1State<Args, UiState>) => Current,\n ): DataModelMigrationChainWithRecover<Current> {\n const wrappedFn = (data: unknown): unknown => {\n if (data !== null && typeof data === \"object\" && \"args\" in data) {\n return fn(data as LegacyV1State<Args, UiState>);\n }\n return data;\n };\n return new DataModelMigrationChainWithRecover<Current>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n upgradeLegacyFn: wrappedFn,\n });\n }\n}\n\n/**\n * Builder entry point for creating DataModel with type-safe migrations.\n *\n * @example\n * // Simple (no migrations):\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)\n * .init(() => ({ numbers: [] }));\n *\n * @example\n * // With migrations:\n * const dataModel = new DataModelBuilder()\n * .from<BlockDataV1>(DATA_MODEL_DEFAULT_VERSION)\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With recover() between migrations — recovered data goes through remaining migrations:\n * const dataModelChain = new DataModelBuilder()\n * .from<BlockDataV1>(DATA_MODEL_DEFAULT_VERSION)\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }));\n *\n * // recover() placed before the v3 migration: recovered data goes through v3\n * const dataModel = dataModelChain\n * .recover((version, data) => {\n * if (version === 'legacy' && isLegacyData(data)) return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With upgradeLegacy() — typed upgrade from BlockModel V1 state:\n * type OldArgs = { inputFile: string };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', selectedTab: 'main' }));\n */\nexport class DataModelBuilder {\n /**\n * Start the migration chain with the given initial data type and version key.\n *\n * @typeParam T - Data type for the initial version\n * @param initialVersion - Version key string (e.g. DATA_MODEL_DEFAULT_VERSION or \"v1\")\n * @returns Migration chain builder\n */\n from<T>(initialVersion: string): DataModelMigrationChain<T> {\n return new DataModelMigrationChain<T>({ 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()` to create a DataModel.\n *\n * @example\n * // With recover() between migrations:\n * // Recovered data (V2) goes through the v2→v3 migration automatically.\n * const dataModel = new DataModelBuilder()\n * .from<V1>(DATA_MODEL_DEFAULT_VERSION)\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === \"legacy\") return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\nexport class DataModel<State> {\n /** Latest version key — O(1) access for the common \"already current\" check. */\n private readonly latestVersion: DataVersionKey;\n /** Maps each known version key to the index of the first step to run from it. O(1) lookup. */\n private readonly stepsByFromVersion: ReadonlyMap<DataVersionKey, number>;\n private readonly steps: MigrationStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex: number;\n /** Transforms legacy V1 model data at the initial version before running migrations. */\n private readonly upgradeLegacyFn?: (data: unknown) => unknown;\n\n private constructor({\n versionChain,\n steps,\n initialDataFn,\n recoverFn = defaultRecover,\n recoverFromIndex,\n upgradeLegacyFn,\n }: BuilderState<State>) {\n if (versionChain.length === 0) {\n throw new Error(\"DataModel requires at least one version key\");\n }\n this.latestVersion = versionChain[versionChain.length - 1];\n this.stepsByFromVersion = new Map(versionChain.map((v, i) => [v, i]));\n this.steps = steps;\n this.initialDataFn = initialDataFn;\n this.recoverFn = recoverFn;\n this.recoverFromIndex = recoverFromIndex ?? steps.length;\n this.upgradeLegacyFn = upgradeLegacyFn;\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.latestVersion;\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.latestVersion, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataMigrationResult<State> {\n // Step 1: call the recover function to get data at the recover point\n let currentData: unknown;\n try {\n currentData = 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 // Step 2: run any migrations that were added after recover() in the chain\n for (let i = this.recoverFromIndex; 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.latestVersion, data: currentData as State };\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n *\n * - If version is in chain, applies needed migrations (O(1) lookup)\n * - If version is unknown, calls recover function then runs remaining migrations\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.latestVersion) {\n return { version: this.latestVersion, data: data as State };\n }\n\n const startIndex = this.stepsByFromVersion.get(fromVersion);\n if (startIndex === undefined) {\n return this.recoverFrom(data, fromVersion);\n }\n\n let currentData: unknown = data;\n\n // Legacy V1 upgrade: detect and transform { args, uiState } at the initial version\n if (startIndex === 0 && this.upgradeLegacyFn) {\n try {\n currentData = this.upgradeLegacyFn(currentData);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Legacy upgrade failed: ${errorMessage}`,\n };\n }\n }\n\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.latestVersion, data: currentData as State };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"block_migrations.cjs","sources":["../src/block_migrations.ts"],"sourcesContent":["import { DATA_MODEL_LEGACY_VERSION } from \"./block_storage\";\n\nexport type DataVersionKey = string;\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/** 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/** Thrown when a migration step fails. */\nexport class DataMigrationError extends Error {\n name = \"DataMigrationError\";\n constructor(message: string) {\n super(message);\n }\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/** Legacy V1 model state shape: { args, uiState } */\nexport type LegacyV1State<Args, UiState> = { args: Args; uiState: UiState };\n\n/** Internal state passed from builder to DataModel */\ntype BuilderState<S> = {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n initialDataFn: () => S;\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n /** Index of the first step to run after recovery. Equals the number of steps\n * present at the time recover() was called. */\n recoverFromIndex?: number;\n};\n\ntype RecoverState = {\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n};\n\n/**\n * Abstract base for both migration chain types.\n * Holds shared state, buildStep() helper, and init().\n * migrate() cannot be shared due to a TypeScript limitation: when the base class\n * migrate() return type is abstract, subclasses cannot narrow it without losing type safety.\n * Each subclass therefore owns its migrate() with the correct concrete return type.\n *\n * @internal\n */\nabstract class MigrationChainBase<Current> {\n protected readonly versionChain: DataVersionKey[];\n protected readonly migrationSteps: MigrationStep[];\n\n protected constructor(state: { versionChain: DataVersionKey[]; steps: MigrationStep[] }) {\n this.versionChain = state.versionChain;\n this.migrationSteps = state.steps;\n }\n\n /** Appends a migration step and returns the new versionChain and steps arrays. */\n protected buildStep<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): { versionChain: DataVersionKey[]; steps: MigrationStep[] } {\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 {\n versionChain: [...this.versionChain, nextVersion],\n steps: [...this.migrationSteps, step],\n };\n }\n\n /** Returns recover-specific fields for DataModel construction. Overridden by WithRecover. */\n protected recoverState(): RecoverState {\n return {};\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * @param initialData - Factory function returning the initial state\n * @returns Finalized DataModel instance\n */\n init(initialData: DataCreateFn<Current>): DataModel<Current> {\n return DataModel[FROM_BUILDER]<Current>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n initialDataFn: initialData,\n ...this.recoverState(),\n });\n }\n}\n\n/**\n * Migration chain after recover() or upgradeLegacy() has been called.\n * Further migrate() calls are allowed; recover() and upgradeLegacy() are not\n * (enforced by type — no such methods on this class).\n *\n * @typeParam Current - Data type at the current point in the chain\n * @internal\n */\nclass DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Current> {\n private readonly recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex?: number;\n\n /** @internal */\n constructor(state: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n }) {\n super(state);\n this.recoverFn = state.recoverFn;\n this.recoverFromIndex = state.recoverFromIndex;\n }\n\n protected override recoverState(): RecoverState {\n return {\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n };\n }\n\n /**\n * Add a migration step. Same semantics as on the base chain.\n * recover() and upgradeLegacy() are not available — one has already been called.\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChainWithRecover<Next> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChainWithRecover<Next>({\n versionChain,\n steps,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n });\n }\n}\n\n/**\n * Migration chain builder.\n * Each migrate() call advances the current data type. recover() can be called once\n * at any point — it removes itself from the returned chain so it cannot be called again.\n * Duplicate version keys throw at runtime.\n *\n * @typeParam Current - Data type at the current point in the migration chain\n * @internal\n */\nclass DataModelMigrationChain<Current> extends MigrationChainBase<Current> {\n /** @internal */\n constructor({\n versionChain,\n steps = [],\n }: {\n versionChain: DataVersionKey[];\n steps?: MigrationStep[];\n }) {\n super({ versionChain, steps });\n }\n\n /**\n * Add a migration step transforming data from the current version to the next.\n *\n * @typeParam Next - Data type of the next version\n * @param nextVersion - Version key to migrate to (must be unique in the chain)\n * @param fn - Migration function\n * @returns Builder with the next version as current\n *\n * @example\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChain<Next> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChain<Next>({ versionChain, steps });\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 must return data of the type at this point in the chain (Current). Any migrate()\n * steps added after recover() will then run on the recovered data.\n *\n * Can only be called once — the returned chain has no recover() method.\n *\n * @param fn - Recovery function returning Current (the type at this chain position)\n * @returns Builder with migrate() and init() but without recover()\n *\n * @example\n * // Recover between migrations — recovered data goes through v3 migration\n * new DataModelBuilder<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === 'legacy') return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\n recover(fn: DataRecoverFn<Current>): DataModelMigrationChainWithRecover<Current> {\n return new DataModelMigrationChainWithRecover<Current>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n recoverFn: fn as (version: DataVersionKey, data: unknown) => unknown,\n recoverFromIndex: this.migrationSteps.length,\n });\n }\n}\n\n/**\n * Initial migration chain returned by `.from()`.\n * Extends DataModelMigrationChain with `upgradeLegacy()` — available only before\n * any `.migrate()` calls, since legacy data always arrives at the initial version.\n *\n * @typeParam Current - Data type at the initial version\n * @internal\n */\nclass DataModelInitialChain<Current> extends DataModelMigrationChain<Current> {\n /**\n * Handle legacy V1 model state ({ args, uiState }) when upgrading a block from\n * BlockModel V1 to BlockModelV3.\n *\n * When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized\n * to the internal default version. This method inserts a migration step from that\n * internal version to the version specified in `.from()`, using the provided typed\n * callback to transform the legacy shape. Non-legacy data passes through unchanged.\n *\n * Must be called right after `.from()` — not available after `.migrate()` calls.\n * Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.\n *\n * Can only be called once — the returned chain has no upgradeLegacy() method.\n * Mutually exclusive with recover().\n *\n * @typeParam Args - Type of the legacy block args\n * @typeParam UiState - Type of the legacy block uiState\n * @param fn - Typed transform from { args, uiState } to Current\n * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()\n *\n * @example\n * type OldArgs = { inputFile: string; threshold: number };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; threshold: number; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * threshold: args.threshold,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', threshold: 0, selectedTab: 'main' }));\n */\n upgradeLegacy<Args, UiState = unknown>(\n fn: (legacy: LegacyV1State<Args, UiState>) => Current,\n ): DataModelMigrationChainWithRecover<Current> {\n const wrappedFn = (data: unknown): unknown => {\n if (data !== null && typeof data === \"object\" && \"args\" in data) {\n return fn(data as LegacyV1State<Args, UiState>);\n }\n return data;\n };\n\n // Insert DATA_MODEL_LEGACY_VERSION as the true first version\n // with a migration step that transforms legacy data to the user's initial version.\n const initialVersion = this.versionChain[0];\n const step: MigrationStep = {\n fromVersion: DATA_MODEL_LEGACY_VERSION,\n toVersion: initialVersion,\n migrate: wrappedFn,\n };\n return new DataModelMigrationChainWithRecover<Current>({\n versionChain: [DATA_MODEL_LEGACY_VERSION, ...this.versionChain],\n steps: [step, ...this.migrationSteps],\n });\n }\n}\n\n/**\n * Builder entry point for creating DataModel with type-safe migrations.\n *\n * @example\n * // Simple (no migrations):\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .init(() => ({ numbers: [] }));\n *\n * @example\n * // With migrations:\n * const dataModel = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With recover() between migrations — recovered data goes through remaining migrations:\n * const dataModelChain = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }));\n *\n * // recover() placed before the v3 migration: recovered data goes through v3\n * const dataModel = dataModelChain\n * .recover((version, data) => {\n * if (version === 'legacy' && isLegacyData(data)) return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With upgradeLegacy() — typed upgrade from BlockModel V1 state:\n * type OldArgs = { inputFile: string };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', selectedTab: 'main' }));\n */\nexport class DataModelBuilder {\n /**\n * Start the migration chain with the given initial data type and version key.\n *\n * @typeParam T - Data type for the initial version\n * @param initialVersion - Version key string (e.g. \"v1\")\n * @returns Migration chain builder\n */\n from<T>(initialVersion: string): DataModelInitialChain<T> {\n return new DataModelInitialChain<T>({ 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()` to create a DataModel.\n *\n * @example\n * // With recover() between migrations:\n * // Recovered data (V2) goes through the v2→v3 migration automatically.\n * const dataModel = new DataModelBuilder()\n * .from<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === \"legacy\") return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\nexport class DataModel<State> {\n /** Latest version key — O(1) access for the common \"already current\" check. */\n private readonly latestVersion: DataVersionKey;\n /** Maps each known version key to the index of the first step to run from it. O(1) lookup. */\n private readonly stepsByFromVersion: ReadonlyMap<DataVersionKey, number>;\n private readonly steps: MigrationStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex: number;\n\n private constructor({\n versionChain,\n steps,\n initialDataFn,\n recoverFn = defaultRecover,\n recoverFromIndex,\n }: BuilderState<State>) {\n if (versionChain.length === 0) {\n throw new Error(\"DataModel requires at least one version key\");\n }\n this.latestVersion = versionChain[versionChain.length - 1];\n this.stepsByFromVersion = new Map(versionChain.map((v, i) => [v, i]));\n this.steps = steps;\n this.initialDataFn = initialDataFn;\n this.recoverFn = recoverFn;\n this.recoverFromIndex = recoverFromIndex ?? steps.length;\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.latestVersion;\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.latestVersion, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataVersioned<State> {\n // Step 1: call the recover function to get data at the recover point\n // Let errors (including DataUnrecoverableError) propagate to the caller.\n let currentData: unknown = this.recoverFn(version, data);\n\n // Step 2: run any migrations that were added after recover() in the chain\n for (let i = this.recoverFromIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n currentData = step.migrate(currentData);\n }\n\n return { version: this.latestVersion, data: currentData as State };\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n *\n * - If version is in chain, applies needed migrations (O(1) lookup)\n * - If version is unknown, attempts recovery; falls back to initial data\n * - If a migration step fails, throws so the caller can preserve original data\n *\n * @param versioned - Data with version tag\n * @returns Migrated data at the latest version\n * @throws If a migration step from a known version fails\n */\n migrate(versioned: DataVersioned<unknown>): DataVersioned<State> {\n const { version: fromVersion, data } = versioned;\n\n if (fromVersion === this.latestVersion) {\n return { version: this.latestVersion, data: data as State };\n }\n\n const startIndex = this.stepsByFromVersion.get(fromVersion);\n if (startIndex === undefined) {\n try {\n return this.recoverFrom(data, fromVersion);\n } catch {\n // Recovery failed (unknown version, recover fn threw, or post-recover\n // migration failed) — reset to initial data rather than blocking the update.\n return this.getDefaultData();\n }\n }\n\n let currentData: unknown = data;\n\n for (let i = startIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n currentData = step.migrate(currentData);\n }\n\n return { version: this.latestVersion, data: currentData as State };\n }\n}\n"],"names":["DATA_MODEL_LEGACY_VERSION"],"mappings":"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"}
|
|
@@ -9,10 +9,11 @@ export type DataVersioned<T> = {
|
|
|
9
9
|
};
|
|
10
10
|
/** Create a DataVersioned wrapper with correct shape */
|
|
11
11
|
export declare function makeDataVersioned<T>(version: DataVersionKey, data: T): DataVersioned<T>;
|
|
12
|
-
/**
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
/** Thrown when a migration step fails. */
|
|
13
|
+
export declare class DataMigrationError extends Error {
|
|
14
|
+
name: string;
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
16
17
|
/** Thrown by recover() to signal unrecoverable data. */
|
|
17
18
|
export declare class DataUnrecoverableError extends Error {
|
|
18
19
|
name: string;
|
|
@@ -53,13 +54,10 @@ type BuilderState<S> = {
|
|
|
53
54
|
/** Index of the first step to run after recovery. Equals the number of steps
|
|
54
55
|
* present at the time recover() was called. */
|
|
55
56
|
recoverFromIndex?: number;
|
|
56
|
-
/** Transforms legacy V1 model data ({ args, uiState }) at the initial version. */
|
|
57
|
-
upgradeLegacyFn?: (data: unknown) => unknown;
|
|
58
57
|
};
|
|
59
58
|
type RecoverState = {
|
|
60
59
|
recoverFn?: (version: DataVersionKey, data: unknown) => unknown;
|
|
61
60
|
recoverFromIndex?: number;
|
|
62
|
-
upgradeLegacyFn?: (data: unknown) => unknown;
|
|
63
61
|
};
|
|
64
62
|
/**
|
|
65
63
|
* Abstract base for both migration chain types.
|
|
@@ -103,14 +101,12 @@ declare abstract class MigrationChainBase<Current> {
|
|
|
103
101
|
declare class DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Current> {
|
|
104
102
|
private readonly recoverFn?;
|
|
105
103
|
private readonly recoverFromIndex?;
|
|
106
|
-
private readonly upgradeLegacyFn?;
|
|
107
104
|
/** @internal */
|
|
108
105
|
constructor(state: {
|
|
109
106
|
versionChain: DataVersionKey[];
|
|
110
107
|
steps: MigrationStep[];
|
|
111
108
|
recoverFn?: (version: DataVersionKey, data: unknown) => unknown;
|
|
112
109
|
recoverFromIndex?: number;
|
|
113
|
-
upgradeLegacyFn?: (data: unknown) => unknown;
|
|
114
110
|
});
|
|
115
111
|
protected recoverState(): RecoverState;
|
|
116
112
|
/**
|
|
@@ -154,10 +150,9 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
154
150
|
* steps added after recover() will then run on the recovered data.
|
|
155
151
|
*
|
|
156
152
|
* Can only be called once — the returned chain has no recover() method.
|
|
157
|
-
* Mutually exclusive with upgradeLegacy().
|
|
158
153
|
*
|
|
159
154
|
* @param fn - Recovery function returning Current (the type at this chain position)
|
|
160
|
-
* @returns Builder with migrate() and init() but without recover()
|
|
155
|
+
* @returns Builder with migrate() and init() but without recover()
|
|
161
156
|
*
|
|
162
157
|
* @example
|
|
163
158
|
* // Recover between migrations — recovered data goes through v3 migration
|
|
@@ -171,18 +166,27 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
171
166
|
* .init(() => ({ count: 0, label: "", description: "" }));
|
|
172
167
|
*/
|
|
173
168
|
recover(fn: DataRecoverFn<Current>): DataModelMigrationChainWithRecover<Current>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Initial migration chain returned by `.from()`.
|
|
172
|
+
* Extends DataModelMigrationChain with `upgradeLegacy()` — available only before
|
|
173
|
+
* any `.migrate()` calls, since legacy data always arrives at the initial version.
|
|
174
|
+
*
|
|
175
|
+
* @typeParam Current - Data type at the initial version
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
declare class DataModelInitialChain<Current> extends DataModelMigrationChain<Current> {
|
|
174
179
|
/**
|
|
175
180
|
* Handle legacy V1 model state ({ args, uiState }) when upgrading a block from
|
|
176
181
|
* BlockModel V1 to BlockModelV3.
|
|
177
182
|
*
|
|
178
|
-
* When a V1 block is upgraded, its stored state `{ args, uiState }`
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
183
|
+
* When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized
|
|
184
|
+
* to the internal default version. This method inserts a migration step from that
|
|
185
|
+
* internal version to the version specified in `.from()`, using the provided typed
|
|
186
|
+
* callback to transform the legacy shape. Non-legacy data passes through unchanged.
|
|
182
187
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* `upgradeLegacy()` will run on the transformed result.
|
|
188
|
+
* Must be called right after `.from()` — not available after `.migrate()` calls.
|
|
189
|
+
* Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.
|
|
186
190
|
*
|
|
187
191
|
* Can only be called once — the returned chain has no upgradeLegacy() method.
|
|
188
192
|
* Mutually exclusive with recover().
|
|
@@ -198,7 +202,7 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
198
202
|
* type BlockData = { inputFile: string; threshold: number; selectedTab: string };
|
|
199
203
|
*
|
|
200
204
|
* const dataModel = new DataModelBuilder()
|
|
201
|
-
* .from<BlockData>(
|
|
205
|
+
* .from<BlockData>("v1")
|
|
202
206
|
* .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
|
|
203
207
|
* inputFile: args.inputFile,
|
|
204
208
|
* threshold: args.threshold,
|
|
@@ -214,13 +218,13 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
214
218
|
* @example
|
|
215
219
|
* // Simple (no migrations):
|
|
216
220
|
* const dataModel = new DataModelBuilder()
|
|
217
|
-
* .from<BlockData>(
|
|
221
|
+
* .from<BlockData>("v1")
|
|
218
222
|
* .init(() => ({ numbers: [] }));
|
|
219
223
|
*
|
|
220
224
|
* @example
|
|
221
225
|
* // With migrations:
|
|
222
226
|
* const dataModel = new DataModelBuilder()
|
|
223
|
-
* .from<BlockDataV1>(
|
|
227
|
+
* .from<BlockDataV1>("v1")
|
|
224
228
|
* .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }))
|
|
225
229
|
* .migrate<BlockDataV3>("v3", (v2) => ({ ...v2, description: '' }))
|
|
226
230
|
* .init(() => ({ numbers: [], labels: [], description: '' }));
|
|
@@ -228,7 +232,7 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
228
232
|
* @example
|
|
229
233
|
* // With recover() between migrations — recovered data goes through remaining migrations:
|
|
230
234
|
* const dataModelChain = new DataModelBuilder()
|
|
231
|
-
* .from<BlockDataV1>(
|
|
235
|
+
* .from<BlockDataV1>("v1")
|
|
232
236
|
* .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }));
|
|
233
237
|
*
|
|
234
238
|
* // recover() placed before the v3 migration: recovered data goes through v3
|
|
@@ -247,7 +251,7 @@ declare class DataModelMigrationChain<Current> extends MigrationChainBase<Curren
|
|
|
247
251
|
* type BlockData = { inputFile: string; selectedTab: string };
|
|
248
252
|
*
|
|
249
253
|
* const dataModel = new DataModelBuilder()
|
|
250
|
-
* .from<BlockData>(
|
|
254
|
+
* .from<BlockData>("v1")
|
|
251
255
|
* .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
|
|
252
256
|
* inputFile: args.inputFile,
|
|
253
257
|
* selectedTab: uiState.selectedTab,
|
|
@@ -259,10 +263,10 @@ export declare class DataModelBuilder {
|
|
|
259
263
|
* Start the migration chain with the given initial data type and version key.
|
|
260
264
|
*
|
|
261
265
|
* @typeParam T - Data type for the initial version
|
|
262
|
-
* @param initialVersion - Version key string (e.g.
|
|
266
|
+
* @param initialVersion - Version key string (e.g. "v1")
|
|
263
267
|
* @returns Migration chain builder
|
|
264
268
|
*/
|
|
265
|
-
from<T>(initialVersion: string):
|
|
269
|
+
from<T>(initialVersion: string): DataModelInitialChain<T>;
|
|
266
270
|
}
|
|
267
271
|
/**
|
|
268
272
|
* DataModel defines the block's data structure, initial values, and migrations.
|
|
@@ -274,7 +278,7 @@ export declare class DataModelBuilder {
|
|
|
274
278
|
* // With recover() between migrations:
|
|
275
279
|
* // Recovered data (V2) goes through the v2→v3 migration automatically.
|
|
276
280
|
* const dataModel = new DataModelBuilder()
|
|
277
|
-
* .from<V1>(
|
|
281
|
+
* .from<V1>("v1")
|
|
278
282
|
* .migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
|
|
279
283
|
* .recover((version, data) => {
|
|
280
284
|
* if (version === "legacy") return transformLegacy(data); // returns V2
|
|
@@ -292,8 +296,6 @@ export declare class DataModel<State> {
|
|
|
292
296
|
private readonly initialDataFn;
|
|
293
297
|
private readonly recoverFn;
|
|
294
298
|
private readonly recoverFromIndex;
|
|
295
|
-
/** Transforms legacy V1 model data at the initial version before running migrations. */
|
|
296
|
-
private readonly upgradeLegacyFn?;
|
|
297
299
|
private constructor();
|
|
298
300
|
/**
|
|
299
301
|
* Internal method for creating DataModel from builder.
|
|
@@ -319,13 +321,14 @@ export declare class DataModel<State> {
|
|
|
319
321
|
* Migrate versioned data from any version to the latest.
|
|
320
322
|
*
|
|
321
323
|
* - If version is in chain, applies needed migrations (O(1) lookup)
|
|
322
|
-
* - If version is unknown,
|
|
323
|
-
* - If migration
|
|
324
|
+
* - If version is unknown, attempts recovery; falls back to initial data
|
|
325
|
+
* - If a migration step fails, throws so the caller can preserve original data
|
|
324
326
|
*
|
|
325
327
|
* @param versioned - Data with version tag
|
|
326
|
-
* @returns
|
|
328
|
+
* @returns Migrated data at the latest version
|
|
329
|
+
* @throws If a migration step from a known version fails
|
|
327
330
|
*/
|
|
328
|
-
migrate(versioned: DataVersioned<unknown>):
|
|
331
|
+
migrate(versioned: DataVersioned<unknown>): DataVersioned<State>;
|
|
329
332
|
}
|
|
330
333
|
export {};
|
|
331
334
|
//# sourceMappingURL=block_migrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_migrations.d.ts","sourceRoot":"","sources":["../src/block_migrations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"block_migrations.d.ts","sourceRoot":"","sources":["../src/block_migrations.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AACpC,MAAM,MAAM,aAAa,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;AACnE,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACtC,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;AAE7E,6CAA6C;AAC7C,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAEvF;AAED,0CAA0C;AAC1C,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,IAAI,SAAwB;gBAChB,OAAO,EAAE,MAAM;CAG5B;AAED,wDAAwD;AACxD,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,IAAI,SAA4B;gBACpB,WAAW,EAAE,cAAc;CAGxC;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAExF;AAED,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,cAAc,CAAC;IAC5B,SAAS,EAAE,cAAc,CAAC;IAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,KAAK,CAE/C,CAAC;AAEF,kDAAkD;AAClD,QAAA,MAAM,YAAY,eAAwB,CAAC;AAE3C,qDAAqD;AACrD,MAAM,MAAM,aAAa,CAAC,IAAI,EAAE,OAAO,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5E,sDAAsD;AACtD,KAAK,YAAY,CAAC,CAAC,IAAI;IACrB,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAChE;oDACgD;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;;;GAQG;AACH,uBAAe,kBAAkB,CAAC,OAAO;IACvC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,cAAc,EAAE,CAAC;IAClD,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,aAAa,EAAE,CAAC;IAEnD,SAAS,aAAa,KAAK,EAAE;QAAE,YAAY,EAAE,cAAc,EAAE,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE;IAKvF,kFAAkF;IAClF,SAAS,CAAC,SAAS,CAAC,IAAI,EACtB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,GAC/B;QAAE,YAAY,EAAE,cAAc,EAAE,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE;IAgB7D,6FAA6F;IAC7F,SAAS,CAAC,YAAY,IAAI,YAAY;IAItC;;;;;OAKG;IACH,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;CAQ7D;AAED;;;;;;;GAOG;AACH,cAAM,kCAAkC,CAAC,OAAO,CAAE,SAAQ,kBAAkB,CAAC,OAAO,CAAC;IACnF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAsD;IACjF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAS;IAE3C,gBAAgB;gBACJ,KAAK,EAAE;QACjB,YAAY,EAAE,cAAc,EAAE,CAAC;QAC/B,KAAK,EAAE,aAAa,EAAE,CAAC;QACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B;cAMkB,YAAY,IAAI,YAAY;IAO/C;;;OAGG;IACH,OAAO,CAAC,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,GAC/B,kCAAkC,CAAC,IAAI,CAAC;CAS5C;AAED;;;;;;;;GAQG;AACH,cAAM,uBAAuB,CAAC,OAAO,CAAE,SAAQ,kBAAkB,CAAC,OAAO,CAAC;IACxE,gBAAgB;gBACJ,EACV,YAAY,EACZ,KAAU,GACX,EAAE;QACD,YAAY,EAAE,cAAc,EAAE,CAAC;QAC/B,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;KACzB;IAID;;;;;;;;;;OAUG;IACH,OAAO,CAAC,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,GAC/B,uBAAuB,CAAC,IAAI,CAAC;IAKhC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,kCAAkC,CAAC,OAAO,CAAC;CAQjF;AAED;;;;;;;GAOG;AACH,cAAM,qBAAqB,CAAC,OAAO,CAAE,SAAQ,uBAAuB,CAAC,OAAO,CAAC;IAC3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,EACnC,EAAE,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,OAAO,GACpD,kCAAkC,CAAC,OAAO,CAAC;CAqB/C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,qBAAa,gBAAgB;IAC3B;;;;;;OAMG;IACH,IAAI,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAAC,CAAC,CAAC;CAG1D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,SAAS,CAAC,KAAK;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAsC;IACzE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsD;IAChF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C,OAAO;IAkBP;;;;OAIG;IACH,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAI9D;;OAEG;IACH,IAAI,OAAO,IAAI,cAAc,CAE5B;IAED;;OAEG;IACH,WAAW,IAAI,KAAK;IAIpB;;;OAGG;IACH,cAAc,IAAI,aAAa,CAAC,KAAK,CAAC;IAItC,OAAO,CAAC,WAAW;IAcnB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC;CA2BjE"}
|