@platforma-sdk/model 1.52.7 → 1.53.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 +145 -45
- package/dist/block_migrations.cjs.map +1 -1
- package/dist/block_migrations.d.ts +88 -26
- package/dist/block_migrations.d.ts.map +1 -1
- package/dist/block_migrations.js +142 -47
- package/dist/block_migrations.js.map +1 -1
- package/dist/block_storage.cjs +13 -6
- package/dist/block_storage.cjs.map +1 -1
- package/dist/block_storage.d.ts +16 -11
- package/dist/block_storage.d.ts.map +1 -1
- package/dist/block_storage.js +13 -7
- package/dist/block_storage.js.map +1 -1
- package/dist/block_storage_vm.cjs +15 -14
- package/dist/block_storage_vm.cjs.map +1 -1
- package/dist/block_storage_vm.d.ts +1 -1
- package/dist/block_storage_vm.d.ts.map +1 -1
- package/dist/block_storage_vm.js +16 -15
- package/dist/block_storage_vm.js.map +1 -1
- package/dist/components/PlMultiSequenceAlignment.cjs.map +1 -1
- package/dist/components/PlMultiSequenceAlignment.d.ts +5 -3
- package/dist/components/PlMultiSequenceAlignment.d.ts.map +1 -1
- package/dist/components/PlMultiSequenceAlignment.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +7 -7
- package/src/block_migrations.test.ts +170 -0
- package/src/block_migrations.ts +207 -62
- package/src/block_storage.test.ts +23 -22
- package/src/block_storage.ts +23 -16
- package/src/block_storage_vm.ts +21 -19
- package/src/components/PlMultiSequenceAlignment.ts +12 -8
- package/src/index.ts +8 -1
|
@@ -3,23 +3,85 @@
|
|
|
3
3
|
var internal = require('./internal.cjs');
|
|
4
4
|
var block_storage = require('./block_storage.cjs');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Helper to define version keys with literal type inference and runtime validation.
|
|
8
|
+
* - Validates that all version values are unique
|
|
9
|
+
* - Eliminates need for `as const` assertion
|
|
10
|
+
*
|
|
11
|
+
* @throws Error if duplicate version values are found
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const Version = defineDataVersions({
|
|
15
|
+
* Initial: 'v1',
|
|
16
|
+
* AddedLabels: 'v2',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* type VersionedData = {
|
|
20
|
+
* [Version.Initial]: DataV1;
|
|
21
|
+
* [Version.AddedLabels]: DataV2;
|
|
22
|
+
* };
|
|
23
|
+
*/
|
|
24
|
+
function defineDataVersions(versions) {
|
|
25
|
+
const values = Object.values(versions);
|
|
26
|
+
const emptyKeys = Object.keys(versions).filter((key) => versions[key] === '');
|
|
27
|
+
if (emptyKeys.length > 0) {
|
|
28
|
+
throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);
|
|
29
|
+
}
|
|
30
|
+
const unique = new Set(values);
|
|
31
|
+
if (unique.size !== values.length) {
|
|
32
|
+
const duplicates = values.filter((v, i) => values.indexOf(v) !== i);
|
|
33
|
+
throw new Error(`Duplicate version values: ${[...new Set(duplicates)].join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
return versions;
|
|
36
|
+
}
|
|
37
|
+
/** Create a DataVersioned wrapper with correct shape */
|
|
38
|
+
function makeDataVersioned(version, data) {
|
|
39
|
+
return { version, data };
|
|
40
|
+
}
|
|
41
|
+
/** Thrown by recover() to signal unrecoverable data. */
|
|
42
|
+
class DataUnrecoverableError extends Error {
|
|
43
|
+
name = 'DataUnrecoverableError';
|
|
44
|
+
constructor(dataVersion) {
|
|
45
|
+
super(`Unknown version '${dataVersion}'`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isDataUnrecoverableError(error) {
|
|
49
|
+
return error instanceof Error && error.name === 'DataUnrecoverableError';
|
|
50
|
+
}
|
|
51
|
+
/** Default recover function for unknown versions */
|
|
52
|
+
const defaultRecover = (version, _data) => {
|
|
53
|
+
throw new DataUnrecoverableError(version);
|
|
54
|
+
};
|
|
6
55
|
/** Internal builder for chaining migrations */
|
|
7
56
|
class DataModelBuilder {
|
|
57
|
+
versionChain;
|
|
8
58
|
migrationSteps;
|
|
9
|
-
|
|
59
|
+
recoverFn;
|
|
60
|
+
constructor(versionChain, steps = [], recoverFn) {
|
|
61
|
+
this.versionChain = versionChain;
|
|
10
62
|
this.migrationSteps = steps;
|
|
63
|
+
this.recoverFn = recoverFn;
|
|
11
64
|
}
|
|
12
|
-
/** Start a migration chain from an initial
|
|
13
|
-
static from() {
|
|
14
|
-
return new DataModelBuilder();
|
|
65
|
+
/** Start a migration chain from an initial version */
|
|
66
|
+
static from(initialVersion) {
|
|
67
|
+
return new DataModelBuilder([initialVersion]);
|
|
68
|
+
}
|
|
69
|
+
/** Add a migration step to the target version */
|
|
70
|
+
migrate(nextVersion, fn) {
|
|
71
|
+
if (this.versionChain.includes(nextVersion)) {
|
|
72
|
+
throw new Error(`Duplicate version '${nextVersion}' in migration chain`);
|
|
73
|
+
}
|
|
74
|
+
const fromVersion = this.versionChain[this.versionChain.length - 1];
|
|
75
|
+
const step = { fromVersion, toVersion: nextVersion, migrate: fn };
|
|
76
|
+
return new DataModelBuilder([...this.versionChain, nextVersion], [...this.migrationSteps, step]);
|
|
15
77
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
return new DataModelBuilder([...this.migrationSteps, fn
|
|
78
|
+
/** Set recovery handler for unknown or unsupported versions */
|
|
79
|
+
recover(fn) {
|
|
80
|
+
return new DataModelBuilder([...this.versionChain], [...this.migrationSteps], fn);
|
|
19
81
|
}
|
|
20
82
|
/** Finalize with initial data, creating the DataModel */
|
|
21
83
|
create(initialData, ..._) {
|
|
22
|
-
return DataModel._fromBuilder(this.migrationSteps, initialData);
|
|
84
|
+
return DataModel._fromBuilder(this.versionChain, this.migrationSteps, initialData, this.recoverFn);
|
|
23
85
|
}
|
|
24
86
|
}
|
|
25
87
|
/**
|
|
@@ -34,37 +96,61 @@ class DataModelBuilder {
|
|
|
34
96
|
* }));
|
|
35
97
|
*
|
|
36
98
|
* // Data model with migrations
|
|
99
|
+
* const Version = defineDataVersions({
|
|
100
|
+
* V1: 'v1',
|
|
101
|
+
* V2: 'v2',
|
|
102
|
+
* V3: 'v3',
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* type VersionedData = {
|
|
106
|
+
* [Version.V1]: { numbers: number[] };
|
|
107
|
+
* [Version.V2]: { numbers: number[]; labels: string[] };
|
|
108
|
+
* [Version.V3]: { numbers: number[]; labels: string[]; description: string };
|
|
109
|
+
* };
|
|
110
|
+
*
|
|
37
111
|
* const dataModel = DataModel
|
|
38
|
-
* .from<
|
|
39
|
-
* .migrate((data) => ({ ...data, labels: [] }))
|
|
40
|
-
* .migrate((data) => ({ ...data, description: '' }))
|
|
41
|
-
* .
|
|
112
|
+
* .from<VersionedData>(Version.V1)
|
|
113
|
+
* .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
|
|
114
|
+
* .migrate(Version.V3, (data) => ({ ...data, description: '' }))
|
|
115
|
+
* .recover((version, data) => {
|
|
116
|
+
* if (version === 'legacy' && typeof data === 'object' && data !== null && 'numbers' in data) {
|
|
117
|
+
* return { numbers: (data as { numbers: number[] }).numbers, labels: [], description: '' };
|
|
118
|
+
* }
|
|
119
|
+
* return defaultRecover(version, data);
|
|
120
|
+
* })
|
|
121
|
+
* .create(() => ({ numbers: [], labels: [], description: '' }));
|
|
42
122
|
*/
|
|
43
123
|
class DataModel {
|
|
124
|
+
versionChain;
|
|
44
125
|
steps;
|
|
45
|
-
|
|
46
|
-
|
|
126
|
+
initialDataFn;
|
|
127
|
+
recoverFn;
|
|
128
|
+
constructor(versionChain, steps, initialData, recover = defaultRecover) {
|
|
129
|
+
if (versionChain.length === 0) {
|
|
130
|
+
throw new Error('DataModel requires at least one version key');
|
|
131
|
+
}
|
|
132
|
+
this.versionChain = versionChain;
|
|
47
133
|
this.steps = steps;
|
|
48
|
-
this.
|
|
134
|
+
this.initialDataFn = initialData;
|
|
135
|
+
this.recoverFn = recover;
|
|
49
136
|
}
|
|
50
137
|
/** Start a migration chain from an initial type */
|
|
51
|
-
static from() {
|
|
52
|
-
return DataModelBuilder.from();
|
|
138
|
+
static from(initialVersion) {
|
|
139
|
+
return DataModelBuilder.from(initialVersion);
|
|
53
140
|
}
|
|
54
141
|
/** Create a data model with just initial data (no migrations) */
|
|
55
|
-
static create(initialData) {
|
|
56
|
-
return new DataModel([], initialData);
|
|
142
|
+
static create(initialData, version = block_storage.DATA_MODEL_DEFAULT_VERSION) {
|
|
143
|
+
return new DataModel([version], [], initialData);
|
|
57
144
|
}
|
|
58
145
|
/** Create from builder (internal use) */
|
|
59
|
-
static _fromBuilder(steps, initialData) {
|
|
60
|
-
return new DataModel(steps, initialData);
|
|
146
|
+
static _fromBuilder(versionChain, steps, initialData, recover) {
|
|
147
|
+
return new DataModel(versionChain, steps, initialData, recover);
|
|
61
148
|
}
|
|
62
149
|
/**
|
|
63
|
-
* Latest version
|
|
64
|
-
* Version 1 = initial state, each migration adds 1.
|
|
150
|
+
* Latest version key.
|
|
65
151
|
*/
|
|
66
152
|
get version() {
|
|
67
|
-
return this.
|
|
153
|
+
return this.versionChain[this.versionChain.length - 1];
|
|
68
154
|
}
|
|
69
155
|
/** Number of migration steps */
|
|
70
156
|
get migrationCount() {
|
|
@@ -72,43 +158,52 @@ class DataModel {
|
|
|
72
158
|
}
|
|
73
159
|
/** Get initial data */
|
|
74
160
|
initialData() {
|
|
75
|
-
return this.
|
|
161
|
+
return this.initialDataFn();
|
|
76
162
|
}
|
|
77
163
|
/** Get default data wrapped with current version */
|
|
78
164
|
getDefaultData() {
|
|
79
|
-
return
|
|
165
|
+
return makeDataVersioned(this.version, this.initialDataFn());
|
|
166
|
+
}
|
|
167
|
+
recoverFrom(data, version) {
|
|
168
|
+
try {
|
|
169
|
+
return { version: this.version, data: this.recoverFn(version, data) };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (isDataUnrecoverableError(error)) {
|
|
173
|
+
return { ...this.getDefaultData(), warning: error.message };
|
|
174
|
+
}
|
|
175
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
176
|
+
return {
|
|
177
|
+
...this.getDefaultData(),
|
|
178
|
+
warning: `Recover failed for version '${version}': ${errorMessage}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
80
181
|
}
|
|
81
182
|
/**
|
|
82
|
-
*
|
|
183
|
+
* Migrate versioned data from any version to the latest.
|
|
83
184
|
* Applies only the migrations needed (skips already-applied ones).
|
|
84
185
|
* If a migration fails, returns default data with a warning.
|
|
85
186
|
*/
|
|
86
|
-
|
|
187
|
+
migrate(versioned) {
|
|
87
188
|
const { version: fromVersion, data } = versioned;
|
|
88
|
-
if (fromVersion > this.version) {
|
|
89
|
-
throw new Error(`Cannot downgrade from version ${fromVersion} to ${this.version}`);
|
|
90
|
-
}
|
|
91
189
|
if (fromVersion === this.version) {
|
|
92
190
|
return { version: this.version, data: data };
|
|
93
191
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const migrationsToApply = this.steps.slice(startIndex);
|
|
192
|
+
const startIndex = this.versionChain.indexOf(fromVersion);
|
|
193
|
+
if (startIndex < 0) {
|
|
194
|
+
return this.recoverFrom(data, fromVersion);
|
|
195
|
+
}
|
|
99
196
|
let currentData = data;
|
|
100
|
-
for (let i =
|
|
101
|
-
const
|
|
102
|
-
const fromVer = stepIndex + 1;
|
|
103
|
-
const toVer = stepIndex + 2;
|
|
197
|
+
for (let i = startIndex; i < this.steps.length; i++) {
|
|
198
|
+
const step = this.steps[i];
|
|
104
199
|
try {
|
|
105
|
-
currentData =
|
|
200
|
+
currentData = step.migrate(currentData);
|
|
106
201
|
}
|
|
107
202
|
catch (error) {
|
|
108
203
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
109
204
|
return {
|
|
110
205
|
...this.getDefaultData(),
|
|
111
|
-
warning: `Migration
|
|
206
|
+
warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,
|
|
112
207
|
};
|
|
113
208
|
}
|
|
114
209
|
}
|
|
@@ -120,12 +215,12 @@ class DataModel {
|
|
|
120
215
|
*
|
|
121
216
|
* All callbacks are prefixed with `__pl_` to indicate internal SDK use:
|
|
122
217
|
* - `__pl_data_initial`: returns initial data for new blocks
|
|
123
|
-
* - `
|
|
218
|
+
* - `__pl_data_migrate`: migrates versioned data from any version to latest
|
|
124
219
|
* - `__pl_storage_initial`: returns initial BlockStorage as JSON string
|
|
125
220
|
*/
|
|
126
221
|
registerCallbacks() {
|
|
127
|
-
internal.tryRegisterCallback('__pl_data_initial', () => this.
|
|
128
|
-
internal.tryRegisterCallback('
|
|
222
|
+
internal.tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());
|
|
223
|
+
internal.tryRegisterCallback('__pl_data_migrate', (versioned) => this.migrate(versioned));
|
|
129
224
|
internal.tryRegisterCallback('__pl_storage_initial', () => {
|
|
130
225
|
const { version, data } = this.getDefaultData();
|
|
131
226
|
const storage = block_storage.createBlockStorage(data, version);
|
|
@@ -135,4 +230,9 @@ class DataModel {
|
|
|
135
230
|
}
|
|
136
231
|
|
|
137
232
|
exports.DataModel = DataModel;
|
|
233
|
+
exports.DataUnrecoverableError = DataUnrecoverableError;
|
|
234
|
+
exports.defaultRecover = defaultRecover;
|
|
235
|
+
exports.defineDataVersions = defineDataVersions;
|
|
236
|
+
exports.isDataUnrecoverableError = isDataUnrecoverableError;
|
|
237
|
+
exports.makeDataVersioned = makeDataVersioned;
|
|
138
238
|
//# sourceMappingURL=block_migrations.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_migrations.cjs","sources":["../src/block_migrations.ts"],"sourcesContent":["import { tryRegisterCallback } from './internal';\nimport { createBlockStorage } from './block_storage';\n\nexport type MigrationFn<From, To> = (prev: Readonly<From>) => To;\n\n/** Versioned data wrapper for persistence */\nexport type Versioned<T> = {\n version: number;\n data: T;\n};\n\n/** Result of upgrade operation, may include warning if migration failed */\nexport type UpgradeResult<T> = Versioned<T> & {\n warning?: string;\n};\n\n/** Internal builder for chaining migrations */\nclass DataModelBuilder<State> {\n private readonly migrationSteps: Array<(x: unknown) => unknown>;\n\n private constructor(steps: Array<(x: unknown) => unknown> = []) {\n this.migrationSteps = steps;\n }\n\n /** Start a migration chain from an initial type */\n static from<T = unknown>(): DataModelBuilder<T> {\n return new DataModelBuilder<T>();\n }\n\n /** Add a migration step */\n migrate<Next>(fn: MigrationFn<State, Next>): DataModelBuilder<Next> {\n return new DataModelBuilder<Next>([...this.migrationSteps, fn as any]);\n }\n\n /** Finalize with initial data, creating the DataModel */\n create<S>(\n initialData: () => S,\n ..._: [State] extends [S] ? [] : [never]\n ): DataModel<S> {\n return DataModel._fromBuilder<S>(this.migrationSteps, initialData);\n }\n}\n\n/**\n * DataModel defines the block's data structure, initial values, and migrations.\n * Used by BlockModelV3 to manage data state.\n *\n * @example\n * // Simple data model (no migrations)\n * const dataModel = DataModel.create<BlockData>(() => ({\n * numbers: [],\n * labels: [],\n * }));\n *\n * // Data model with migrations\n * const dataModel = DataModel\n * .from<V1>()\n * .migrate((data) => ({ ...data, labels: [] })) // v1 → v2\n * .migrate((data) => ({ ...data, description: '' })) // v2 → v3\n * .create<BlockData>(() => ({ numbers: [], labels: [], description: '' }));\n */\nexport class DataModel<State> {\n private readonly steps: Array<(x: unknown) => unknown>;\n private readonly _initialData: () => State;\n\n private constructor(steps: Array<(x: unknown) => unknown>, initialData: () => State) {\n this.steps = steps;\n this._initialData = initialData;\n }\n\n /** Start a migration chain from an initial type */\n static from<S>(): DataModelBuilder<S> {\n return DataModelBuilder.from<S>();\n }\n\n /** Create a data model with just initial data (no migrations) */\n static create<S>(initialData: () => S): DataModel<S> {\n return new DataModel<S>([], initialData);\n }\n\n /** Create from builder (internal use) */\n static _fromBuilder<S>(\n steps: Array<(x: unknown) => unknown>,\n initialData: () => S,\n ): DataModel<S> {\n return new DataModel<S>(steps, initialData);\n }\n\n /**\n * Latest version number.\n * Version 1 = initial state, each migration adds 1.\n */\n get version(): number {\n return this.steps.length + 1;\n }\n\n /** Number of migration steps */\n get migrationCount(): number {\n return this.steps.length;\n }\n\n /** Get initial data */\n initialData(): State {\n return this._initialData();\n }\n\n /** Get default data wrapped with current version */\n getDefaultData(): Versioned<State> {\n return { version: this.version, data: this._initialData() };\n }\n\n /**\n * Upgrade versioned data from any version to the latest.\n * Applies only the migrations needed (skips already-applied ones).\n * If a migration fails, returns default data with a warning.\n */\n upgrade(versioned: Versioned<unknown>): UpgradeResult<State> {\n const { version: fromVersion, data } = versioned;\n\n if (fromVersion > this.version) {\n throw new Error(\n `Cannot downgrade from version ${fromVersion} to ${this.version}`,\n );\n }\n\n if (fromVersion === this.version) {\n return { version: this.version, data: data as State };\n }\n\n // Apply migrations starting from (fromVersion - 1) index\n // Version 1 -> no migrations applied yet -> start at index 0\n // Version 2 -> migration[0] already applied -> start at index 1\n const startIndex = fromVersion - 1;\n const migrationsToApply = this.steps.slice(startIndex);\n\n let currentData: unknown = data;\n for (let i = 0; i < migrationsToApply.length; i++) {\n const stepIndex = startIndex + i;\n const fromVer = stepIndex + 1;\n const toVer = stepIndex + 2;\n try {\n currentData = migrationsToApply[i](currentData);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Migration v${fromVer}→v${toVer} failed: ${errorMessage}`,\n };\n }\n }\n\n return { version: this.version, data: currentData as State };\n }\n\n /**\n * Register callbacks for use in the VM.\n * Called by BlockModelV3.create() to set up internal callbacks.\n *\n * All callbacks are prefixed with `__pl_` to indicate internal SDK use:\n * - `__pl_data_initial`: returns initial data for new blocks\n * - `__pl_data_upgrade`: upgrades versioned data from any version to latest\n * - `__pl_storage_initial`: returns initial BlockStorage as JSON string\n */\n registerCallbacks(): void {\n tryRegisterCallback('__pl_data_initial', () => this._initialData());\n tryRegisterCallback('__pl_data_upgrade', (versioned: Versioned<unknown>) => this.upgrade(versioned));\n tryRegisterCallback('__pl_storage_initial', () => {\n const { version, data } = this.getDefaultData();\n const storage = createBlockStorage(data, version);\n return JSON.stringify(storage);\n });\n }\n}\n"],"names":["tryRegisterCallback","createBlockStorage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"block_migrations.cjs","sources":["../src/block_migrations.ts"],"sourcesContent":["import { tryRegisterCallback } from './internal';\nimport { createBlockStorage, DATA_MODEL_DEFAULT_VERSION } from './block_storage';\n\nexport type DataVersionKey = string;\nexport type DataVersionMap = Record<string, unknown>;\nexport type DataMigrateFn<From, To> = (prev: Readonly<From>) => To;\nexport type DataCreateFn<T> = () => T;\nexport type DataRecoverFn<T> = (version: DataVersionKey, data: unknown) => T;\n\n/**\n * Helper to define version keys with literal type inference and runtime validation.\n * - Validates that all version values are unique\n * - Eliminates need for `as const` assertion\n *\n * @throws Error if duplicate version values are found\n *\n * @example\n * const Version = defineDataVersions({\n * Initial: 'v1',\n * AddedLabels: 'v2',\n * });\n *\n * type VersionedData = {\n * [Version.Initial]: DataV1;\n * [Version.AddedLabels]: DataV2;\n * };\n */\nexport function defineDataVersions<const T extends Record<string, string>>(versions: T): T {\n const values = Object.values(versions);\n const emptyKeys = Object.keys(versions).filter((key) => versions[key] === '');\n if (emptyKeys.length > 0) {\n throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);\n }\n const unique = new Set(values);\n if (unique.size !== values.length) {\n const duplicates = values.filter((v, i) => values.indexOf(v) !== i);\n throw new Error(`Duplicate version values: ${[...new Set(duplicates)].join(', ')}`);\n }\n return versions;\n}\n\n/** Versioned data wrapper for persistence */\nexport type DataVersioned<T> = {\n version: DataVersionKey;\n data: T;\n};\n\n/** Create a DataVersioned wrapper with correct shape */\nexport function makeDataVersioned<T>(version: DataVersionKey, data: T): DataVersioned<T> {\n return { version, data };\n}\n\n/** Result of migration operation, may include warning if migration failed */\nexport type DataMigrationResult<T> = DataVersioned<T> & {\n warning?: string;\n};\n\n/** Thrown by recover() to signal unrecoverable data. */\nexport class DataUnrecoverableError extends Error {\n name = 'DataUnrecoverableError';\n constructor(dataVersion: DataVersionKey) {\n super(`Unknown version '${dataVersion}'`);\n }\n}\n\nexport function isDataUnrecoverableError(error: unknown): error is DataUnrecoverableError {\n return error instanceof Error && error.name === 'DataUnrecoverableError';\n}\n\ntype MigrationStep = {\n fromVersion: DataVersionKey;\n toVersion: DataVersionKey;\n migrate: (data: unknown) => unknown;\n};\n\n/** Default recover function for unknown versions */\nexport const defaultRecover: DataRecoverFn<never> = (version, _data) => {\n throw new DataUnrecoverableError(version);\n};\n\n/** Internal builder for chaining migrations */\nclass DataModelBuilder<\n VersionedData extends DataVersionMap,\n CurrentVersion extends keyof VersionedData & string,\n> {\n private readonly versionChain: DataVersionKey[];\n private readonly migrationSteps: MigrationStep[];\n private readonly recoverFn?: DataRecoverFn<VersionedData[CurrentVersion]>;\n\n private constructor(\n versionChain: DataVersionKey[],\n steps: MigrationStep[] = [],\n recoverFn?: DataRecoverFn<VersionedData[CurrentVersion]>,\n ) {\n this.versionChain = versionChain;\n this.migrationSteps = steps;\n this.recoverFn = recoverFn;\n }\n\n /** Start a migration chain from an initial version */\n static from<\n VersionedData extends DataVersionMap,\n InitialVersion extends keyof VersionedData & string = keyof VersionedData & string,\n >(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion> {\n return new DataModelBuilder<VersionedData, InitialVersion>([initialVersion]);\n }\n\n /** Add a migration step to the target version */\n migrate<NextVersion extends keyof VersionedData & string>(\n nextVersion: NextVersion,\n fn: DataMigrateFn<VersionedData[CurrentVersion], VersionedData[NextVersion]>,\n ): DataModelBuilder<VersionedData, NextVersion> {\n if (this.versionChain.includes(nextVersion)) {\n throw new Error(`Duplicate version '${nextVersion}' in migration chain`);\n }\n const fromVersion = this.versionChain[this.versionChain.length - 1];\n const step: MigrationStep = { fromVersion, toVersion: nextVersion, migrate: fn as (data: unknown) => unknown };\n return new DataModelBuilder<VersionedData, NextVersion>(\n [...this.versionChain, nextVersion],\n [...this.migrationSteps, step],\n );\n }\n\n /** Set recovery handler for unknown or unsupported versions */\n recover(\n fn: DataRecoverFn<VersionedData[CurrentVersion]>,\n ): DataModelBuilder<VersionedData, CurrentVersion> {\n return new DataModelBuilder<VersionedData, CurrentVersion>(\n [...this.versionChain],\n [...this.migrationSteps],\n fn,\n );\n }\n\n /** Finalize with initial data, creating the DataModel */\n create<S extends VersionedData[CurrentVersion]>(\n initialData: DataCreateFn<S>,\n ..._: [VersionedData[CurrentVersion]] extends [S] ? [] : [never]\n ): DataModel<S> {\n return DataModel._fromBuilder<S>(\n this.versionChain,\n this.migrationSteps,\n initialData,\n this.recoverFn as DataRecoverFn<S> | undefined,\n );\n }\n}\n\n/**\n * DataModel defines the block's data structure, initial values, and migrations.\n * Used by BlockModelV3 to manage data state.\n *\n * @example\n * // Simple data model (no migrations)\n * const dataModel = DataModel.create<BlockData>(() => ({\n * numbers: [],\n * labels: [],\n * }));\n *\n * // Data model with migrations\n * const Version = defineDataVersions({\n * V1: 'v1',\n * V2: 'v2',\n * V3: 'v3',\n * });\n *\n * type VersionedData = {\n * [Version.V1]: { numbers: number[] };\n * [Version.V2]: { numbers: number[]; labels: string[] };\n * [Version.V3]: { numbers: number[]; labels: string[]; description: string };\n * };\n *\n * const dataModel = DataModel\n * .from<VersionedData>(Version.V1)\n * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))\n * .migrate(Version.V3, (data) => ({ ...data, description: '' }))\n * .recover((version, data) => {\n * if (version === 'legacy' && typeof data === 'object' && data !== null && 'numbers' in data) {\n * return { numbers: (data as { numbers: number[] }).numbers, labels: [], description: '' };\n * }\n * return defaultRecover(version, data);\n * })\n * .create(() => ({ numbers: [], labels: [], description: '' }));\n */\nexport class DataModel<State> {\n private readonly versionChain: DataVersionKey[];\n private readonly steps: MigrationStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: DataRecoverFn<State>;\n\n private constructor(\n versionChain: DataVersionKey[],\n steps: MigrationStep[],\n initialData: () => State,\n recover: DataRecoverFn<State> = defaultRecover as DataRecoverFn<State>,\n ) {\n if (versionChain.length === 0) {\n throw new Error('DataModel requires at least one version key');\n }\n this.versionChain = versionChain;\n this.steps = steps;\n this.initialDataFn = initialData;\n this.recoverFn = recover;\n }\n\n /** Start a migration chain from an initial type */\n static from<\n VersionedData extends DataVersionMap,\n InitialVersion extends keyof VersionedData & string = keyof VersionedData & string,\n >(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion> {\n return DataModelBuilder.from<VersionedData, InitialVersion>(initialVersion);\n }\n\n /** Create a data model with just initial data (no migrations) */\n static create<S>(initialData: () => S, version: DataVersionKey = DATA_MODEL_DEFAULT_VERSION): DataModel<S> {\n return new DataModel<S>([version], [], initialData);\n }\n\n /** Create from builder (internal use) */\n static _fromBuilder<S>(\n versionChain: DataVersionKey[],\n steps: MigrationStep[],\n initialData: () => S,\n recover?: DataRecoverFn<S>,\n ): DataModel<S> {\n return new DataModel<S>(versionChain, steps, initialData, recover);\n }\n\n /**\n * Latest version key.\n */\n get version(): DataVersionKey {\n return this.versionChain[this.versionChain.length - 1];\n }\n\n /** Number of migration steps */\n get migrationCount(): number {\n return this.steps.length;\n }\n\n /** Get initial data */\n initialData(): State {\n return this.initialDataFn();\n }\n\n /** Get default data wrapped with current version */\n getDefaultData(): DataVersioned<State> {\n return makeDataVersioned(this.version, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataMigrationResult<State> {\n try {\n return { version: this.version, data: this.recoverFn(version, data) };\n } catch (error) {\n if (isDataUnrecoverableError(error)) {\n return { ...this.getDefaultData(), warning: error.message };\n }\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Recover failed for version '${version}': ${errorMessage}`,\n };\n }\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n * Applies only the migrations needed (skips already-applied ones).\n * If a migration fails, returns default data with a warning.\n */\n migrate(versioned: DataVersioned<unknown>): DataMigrationResult<State> {\n const { version: fromVersion, data } = versioned;\n\n if (fromVersion === this.version) {\n return { version: this.version, data: data as State };\n }\n\n const startIndex = this.versionChain.indexOf(fromVersion);\n if (startIndex < 0) {\n return this.recoverFrom(data, fromVersion);\n }\n\n let currentData: unknown = data;\n for (let i = startIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n try {\n currentData = step.migrate(currentData);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,\n };\n }\n }\n\n return { version: this.version, data: currentData as State };\n }\n\n /**\n * Register callbacks for use in the VM.\n * Called by BlockModelV3.create() to set up internal callbacks.\n *\n * All callbacks are prefixed with `__pl_` to indicate internal SDK use:\n * - `__pl_data_initial`: returns initial data for new blocks\n * - `__pl_data_migrate`: migrates versioned data from any version to latest\n * - `__pl_storage_initial`: returns initial BlockStorage as JSON string\n */\n registerCallbacks(): void {\n tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());\n tryRegisterCallback('__pl_data_migrate', (versioned: DataVersioned<unknown>) => this.migrate(versioned));\n tryRegisterCallback('__pl_storage_initial', () => {\n const { version, data } = this.getDefaultData();\n const storage = createBlockStorage(data, version);\n return JSON.stringify(storage);\n });\n }\n}\n"],"names":["DATA_MODEL_DEFAULT_VERSION","tryRegisterCallback","createBlockStorage"],"mappings":"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"}
|
|
@@ -1,23 +1,65 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type DataVersionKey = string;
|
|
2
|
+
export type DataVersionMap = Record<string, unknown>;
|
|
3
|
+
export type DataMigrateFn<From, To> = (prev: Readonly<From>) => To;
|
|
4
|
+
export type DataCreateFn<T> = () => T;
|
|
5
|
+
export type DataRecoverFn<T> = (version: DataVersionKey, data: unknown) => T;
|
|
6
|
+
/**
|
|
7
|
+
* Helper to define version keys with literal type inference and runtime validation.
|
|
8
|
+
* - Validates that all version values are unique
|
|
9
|
+
* - Eliminates need for `as const` assertion
|
|
10
|
+
*
|
|
11
|
+
* @throws Error if duplicate version values are found
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const Version = defineDataVersions({
|
|
15
|
+
* Initial: 'v1',
|
|
16
|
+
* AddedLabels: 'v2',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* type VersionedData = {
|
|
20
|
+
* [Version.Initial]: DataV1;
|
|
21
|
+
* [Version.AddedLabels]: DataV2;
|
|
22
|
+
* };
|
|
23
|
+
*/
|
|
24
|
+
export declare function defineDataVersions<const T extends Record<string, string>>(versions: T): T;
|
|
2
25
|
/** Versioned data wrapper for persistence */
|
|
3
|
-
export type
|
|
4
|
-
version:
|
|
26
|
+
export type DataVersioned<T> = {
|
|
27
|
+
version: DataVersionKey;
|
|
5
28
|
data: T;
|
|
6
29
|
};
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
30
|
+
/** Create a DataVersioned wrapper with correct shape */
|
|
31
|
+
export declare function makeDataVersioned<T>(version: DataVersionKey, data: T): DataVersioned<T>;
|
|
32
|
+
/** Result of migration operation, may include warning if migration failed */
|
|
33
|
+
export type DataMigrationResult<T> = DataVersioned<T> & {
|
|
9
34
|
warning?: string;
|
|
10
35
|
};
|
|
36
|
+
/** Thrown by recover() to signal unrecoverable data. */
|
|
37
|
+
export declare class DataUnrecoverableError extends Error {
|
|
38
|
+
name: string;
|
|
39
|
+
constructor(dataVersion: DataVersionKey);
|
|
40
|
+
}
|
|
41
|
+
export declare function isDataUnrecoverableError(error: unknown): error is DataUnrecoverableError;
|
|
42
|
+
type MigrationStep = {
|
|
43
|
+
fromVersion: DataVersionKey;
|
|
44
|
+
toVersion: DataVersionKey;
|
|
45
|
+
migrate: (data: unknown) => unknown;
|
|
46
|
+
};
|
|
47
|
+
/** Default recover function for unknown versions */
|
|
48
|
+
export declare const defaultRecover: DataRecoverFn<never>;
|
|
11
49
|
/** Internal builder for chaining migrations */
|
|
12
|
-
declare class DataModelBuilder<
|
|
50
|
+
declare class DataModelBuilder<VersionedData extends DataVersionMap, CurrentVersion extends keyof VersionedData & string> {
|
|
51
|
+
private readonly versionChain;
|
|
13
52
|
private readonly migrationSteps;
|
|
53
|
+
private readonly recoverFn?;
|
|
14
54
|
private constructor();
|
|
15
|
-
/** Start a migration chain from an initial
|
|
16
|
-
static from<
|
|
17
|
-
/** Add a migration step */
|
|
18
|
-
migrate<
|
|
55
|
+
/** Start a migration chain from an initial version */
|
|
56
|
+
static from<VersionedData extends DataVersionMap, InitialVersion extends keyof VersionedData & string = keyof VersionedData & string>(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion>;
|
|
57
|
+
/** Add a migration step to the target version */
|
|
58
|
+
migrate<NextVersion extends keyof VersionedData & string>(nextVersion: NextVersion, fn: DataMigrateFn<VersionedData[CurrentVersion], VersionedData[NextVersion]>): DataModelBuilder<VersionedData, NextVersion>;
|
|
59
|
+
/** Set recovery handler for unknown or unsupported versions */
|
|
60
|
+
recover(fn: DataRecoverFn<VersionedData[CurrentVersion]>): DataModelBuilder<VersionedData, CurrentVersion>;
|
|
19
61
|
/** Finalize with initial data, creating the DataModel */
|
|
20
|
-
create<S>(initialData:
|
|
62
|
+
create<S extends VersionedData[CurrentVersion]>(initialData: DataCreateFn<S>, ..._: [VersionedData[CurrentVersion]] extends [S] ? [] : [never]): DataModel<S>;
|
|
21
63
|
}
|
|
22
64
|
/**
|
|
23
65
|
* DataModel defines the block's data structure, initial values, and migrations.
|
|
@@ -31,46 +73,66 @@ declare class DataModelBuilder<State> {
|
|
|
31
73
|
* }));
|
|
32
74
|
*
|
|
33
75
|
* // Data model with migrations
|
|
76
|
+
* const Version = defineDataVersions({
|
|
77
|
+
* V1: 'v1',
|
|
78
|
+
* V2: 'v2',
|
|
79
|
+
* V3: 'v3',
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* type VersionedData = {
|
|
83
|
+
* [Version.V1]: { numbers: number[] };
|
|
84
|
+
* [Version.V2]: { numbers: number[]; labels: string[] };
|
|
85
|
+
* [Version.V3]: { numbers: number[]; labels: string[]; description: string };
|
|
86
|
+
* };
|
|
87
|
+
*
|
|
34
88
|
* const dataModel = DataModel
|
|
35
|
-
* .from<
|
|
36
|
-
* .migrate((data) => ({ ...data, labels: [] }))
|
|
37
|
-
* .migrate((data) => ({ ...data, description: '' }))
|
|
38
|
-
* .
|
|
89
|
+
* .from<VersionedData>(Version.V1)
|
|
90
|
+
* .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
|
|
91
|
+
* .migrate(Version.V3, (data) => ({ ...data, description: '' }))
|
|
92
|
+
* .recover((version, data) => {
|
|
93
|
+
* if (version === 'legacy' && typeof data === 'object' && data !== null && 'numbers' in data) {
|
|
94
|
+
* return { numbers: (data as { numbers: number[] }).numbers, labels: [], description: '' };
|
|
95
|
+
* }
|
|
96
|
+
* return defaultRecover(version, data);
|
|
97
|
+
* })
|
|
98
|
+
* .create(() => ({ numbers: [], labels: [], description: '' }));
|
|
39
99
|
*/
|
|
40
100
|
export declare class DataModel<State> {
|
|
101
|
+
private readonly versionChain;
|
|
41
102
|
private readonly steps;
|
|
42
|
-
private readonly
|
|
103
|
+
private readonly initialDataFn;
|
|
104
|
+
private readonly recoverFn;
|
|
43
105
|
private constructor();
|
|
44
106
|
/** Start a migration chain from an initial type */
|
|
45
|
-
static from<
|
|
107
|
+
static from<VersionedData extends DataVersionMap, InitialVersion extends keyof VersionedData & string = keyof VersionedData & string>(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion>;
|
|
46
108
|
/** Create a data model with just initial data (no migrations) */
|
|
47
|
-
static create<S>(initialData: () => S): DataModel<S>;
|
|
109
|
+
static create<S>(initialData: () => S, version?: DataVersionKey): DataModel<S>;
|
|
48
110
|
/** Create from builder (internal use) */
|
|
49
|
-
static _fromBuilder<S>(
|
|
111
|
+
static _fromBuilder<S>(versionChain: DataVersionKey[], steps: MigrationStep[], initialData: () => S, recover?: DataRecoverFn<S>): DataModel<S>;
|
|
50
112
|
/**
|
|
51
|
-
* Latest version
|
|
52
|
-
* Version 1 = initial state, each migration adds 1.
|
|
113
|
+
* Latest version key.
|
|
53
114
|
*/
|
|
54
|
-
get version():
|
|
115
|
+
get version(): DataVersionKey;
|
|
55
116
|
/** Number of migration steps */
|
|
56
117
|
get migrationCount(): number;
|
|
57
118
|
/** Get initial data */
|
|
58
119
|
initialData(): State;
|
|
59
120
|
/** Get default data wrapped with current version */
|
|
60
|
-
getDefaultData():
|
|
121
|
+
getDefaultData(): DataVersioned<State>;
|
|
122
|
+
private recoverFrom;
|
|
61
123
|
/**
|
|
62
|
-
*
|
|
124
|
+
* Migrate versioned data from any version to the latest.
|
|
63
125
|
* Applies only the migrations needed (skips already-applied ones).
|
|
64
126
|
* If a migration fails, returns default data with a warning.
|
|
65
127
|
*/
|
|
66
|
-
|
|
128
|
+
migrate(versioned: DataVersioned<unknown>): DataMigrationResult<State>;
|
|
67
129
|
/**
|
|
68
130
|
* Register callbacks for use in the VM.
|
|
69
131
|
* Called by BlockModelV3.create() to set up internal callbacks.
|
|
70
132
|
*
|
|
71
133
|
* All callbacks are prefixed with `__pl_` to indicate internal SDK use:
|
|
72
134
|
* - `__pl_data_initial`: returns initial data for new blocks
|
|
73
|
-
* - `
|
|
135
|
+
* - `__pl_data_migrate`: migrates versioned data from any version to latest
|
|
74
136
|
* - `__pl_storage_initial`: returns initial BlockStorage as JSON string
|
|
75
137
|
*/
|
|
76
138
|
registerCallbacks(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_migrations.d.ts","sourceRoot":"","sources":["../src/block_migrations.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"block_migrations.d.ts","sourceRoot":"","sources":["../src/block_migrations.ts"],"names":[],"mappings":"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"}
|