@twin.org/entity-storage-models 0.0.3-next.20 → 0.0.3-next.22
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/es/factories/schemaMigrationFactory.js +17 -0
- package/dist/es/factories/schemaMigrationFactory.js.map +1 -0
- package/dist/es/helpers/migrationHelper.js +102 -129
- package/dist/es/helpers/migrationHelper.js.map +1 -1
- package/dist/es/index.js +4 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IResolvedMigrationStep.js +2 -0
- package/dist/es/models/IResolvedMigrationStep.js.map +1 -0
- package/dist/es/models/ISchemaMigration.js +2 -0
- package/dist/es/models/ISchemaMigration.js.map +1 -0
- package/dist/types/factories/schemaMigrationFactory.d.ts +14 -0
- package/dist/types/helpers/migrationHelper.d.ts +36 -43
- package/dist/types/index.d.ts +3 -0
- package/dist/types/models/IResolvedMigrationStep.d.ts +36 -0
- package/dist/types/models/ISchemaMigration.d.ts +27 -0
- package/docs/changelog.md +14 -0
- package/docs/reference/classes/MigrationHelper.md +62 -152
- package/docs/reference/index.md +3 -0
- package/docs/reference/interfaces/IResolvedMigrationStep.md +82 -0
- package/docs/reference/interfaces/ISchemaMigration.md +65 -0
- package/docs/reference/variables/SchemaMigrationFactory.md +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright 2026 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { Factory } from "@twin.org/core";
|
|
4
|
+
/**
|
|
5
|
+
* Factory for optional per-step migration overrides.
|
|
6
|
+
*
|
|
7
|
+
* Only register an entry when a version step requires property renames or a custom
|
|
8
|
+
* transform hook. For purely structural changes (add/remove/type-change) the
|
|
9
|
+
* SchemaVersionService diffs the two versioned schema classes automatically
|
|
10
|
+
* without needing any factory entry.
|
|
11
|
+
*
|
|
12
|
+
* Keys follow the convention "<BaseSchemaName>_<fromVersion>_<toVersion>",
|
|
13
|
+
* for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
16
|
+
export const SchemaMigrationFactory = Factory.createFactory("schema-migration");
|
|
17
|
+
//# sourceMappingURL=schemaMigrationFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaMigrationFactory.js","sourceRoot":"","sources":["../../../src/factories/schemaMigrationFactory.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC;;;;;;;;;;GAUG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAmB,kBAAkB,CAAC,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Factory } from \"@twin.org/core\";\nimport type { ISchemaMigration } from \"../models/ISchemaMigration.js\";\n\n/**\n * Factory for optional per-step migration overrides.\n *\n * Only register an entry when a version step requires property renames or a custom\n * transform hook. For purely structural changes (add/remove/type-change) the\n * SchemaVersionService diffs the two versioned schema classes automatically\n * without needing any factory entry.\n *\n * Keys follow the convention \"<BaseSchemaName>_<fromVersion>_<toVersion>\",\n * for example \"MyEntity_0_1\" for the step that migrates MyEntity from version 0 to 1.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const SchemaMigrationFactory = Factory.createFactory<ISchemaMigration>(\"schema-migration\");\n"]}
|
|
@@ -4,7 +4,9 @@ import { ContextIdStore } from "@twin.org/context";
|
|
|
4
4
|
import { Coerce, GeneralError, Is, ObjectHelper } from "@twin.org/core";
|
|
5
5
|
import { EntitySchemaDiffHelper, EntitySchemaPropertyType } from "@twin.org/entity";
|
|
6
6
|
/**
|
|
7
|
-
* Helper class for performing schema migrations between two connectors.
|
|
7
|
+
* Helper class for performing entity schema migrations between two connectors.
|
|
8
|
+
* The chain-based API (migrateWithChain / applyEntityChain) is the single migration
|
|
9
|
+
* path: a chain of one step covers the same case as a traditional single-step migration.
|
|
8
10
|
*/
|
|
9
11
|
export class MigrationHelper {
|
|
10
12
|
/**
|
|
@@ -12,126 +14,16 @@ export class MigrationHelper {
|
|
|
12
14
|
*/
|
|
13
15
|
static CLASS_NAME = "MigrationHelper";
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param targetEntitySchemaName The name of the new entity schema.
|
|
18
|
-
* @param renames An optional list of property renames to apply during migration.
|
|
19
|
-
* @param options Options controlling migration behaviour.
|
|
20
|
-
* @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.
|
|
21
|
-
* @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.
|
|
22
|
-
*/
|
|
23
|
-
static async migrate(sourceConnector, targetEntitySchemaName, renames, options, loggingComponentType) {
|
|
24
|
-
let targetConnector;
|
|
25
|
-
try {
|
|
26
|
-
// We use the migration method to create the new connector as it will use a temporary storage location if necessary
|
|
27
|
-
targetConnector = await sourceConnector.createTargetConnector(targetEntitySchemaName);
|
|
28
|
-
// Startup both connectors to ensure they are ready for the migration, this will call bootstrap and start if they are defined.
|
|
29
|
-
await MigrationHelper.startupConnector(sourceConnector, loggingComponentType);
|
|
30
|
-
await MigrationHelper.startupConnector(targetConnector, loggingComponentType);
|
|
31
|
-
// Get the unique partition context ids to run the migration for each partition.
|
|
32
|
-
let partitionContextIds = await sourceConnector.getPartitionContextIds();
|
|
33
|
-
// If there are no partitions, we still want to run the migration once to handle the schema changes,
|
|
34
|
-
// so we create a single empty context for the migration to run in.
|
|
35
|
-
if (!Is.arrayValue(partitionContextIds)) {
|
|
36
|
-
partitionContextIds ??= [];
|
|
37
|
-
partitionContextIds.push({});
|
|
38
|
-
}
|
|
39
|
-
// Get the schemas
|
|
40
|
-
const sourceSchema = sourceConnector.getSchema();
|
|
41
|
-
const targetSchema = targetConnector.getSchema();
|
|
42
|
-
// Get the schema diff between the source and target schemas.
|
|
43
|
-
const schemaDiff = EntitySchemaDiffHelper.diff(sourceSchema.properties ?? [], targetSchema.properties ?? [], renames);
|
|
44
|
-
// Only perform a migration if the schemas have changed
|
|
45
|
-
if (EntitySchemaDiffHelper.hasChanges(schemaDiff)) {
|
|
46
|
-
// Perform the migration, which will read from the current store and write to the target connector's store.
|
|
47
|
-
const migrated = await MigrationHelper.migrateEntities(sourceConnector, targetConnector, partitionContextIds, schemaDiff, options);
|
|
48
|
-
// The migration is now complete, finalize the migration which could entail
|
|
49
|
-
// renaming of resources etc, determined by the implementation of finalizeMigration.
|
|
50
|
-
// it also needs to stop and teardown any resources no longer in use after the migration,
|
|
51
|
-
// such as the old connector and its underlying storage.
|
|
52
|
-
const finalConnector = await sourceConnector.finalizeMigration(targetConnector, options, loggingComponentType);
|
|
53
|
-
return {
|
|
54
|
-
finalConnector,
|
|
55
|
-
migrated
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
finalConnector: undefined,
|
|
60
|
-
migrated: 0
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
await sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);
|
|
65
|
-
throw new GeneralError(MigrationHelper.CLASS_NAME, "migrationFailed", undefined, error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Generic per-partition migration loop.
|
|
70
|
-
* @param source Connector to read from (current schema, already bootstrapped).
|
|
71
|
-
* @param target Connector to write to (new schema, already bootstrapped).
|
|
72
|
-
* @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.
|
|
73
|
-
* @param schemaDiff The schema diff.
|
|
74
|
-
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
75
|
-
* @returns The number of entities successfully migrated.
|
|
76
|
-
*/
|
|
77
|
-
static async migrateEntities(source, target, partitionContextIds, schemaDiff, options) {
|
|
78
|
-
let migrated = 0;
|
|
79
|
-
await options?.onStepProgress?.("migrationStart", 0, 0);
|
|
80
|
-
const effectivePartitions = partitionContextIds.length > 0 ? partitionContextIds : [{}];
|
|
81
|
-
for (let i = 0; i < effectivePartitions.length; i++) {
|
|
82
|
-
await ContextIdStore.run(effectivePartitions[i], async () => {
|
|
83
|
-
await options?.onStepProgress?.("partitionStart", effectivePartitions.length, i);
|
|
84
|
-
migrated += await MigrationHelper.migratePartition(source, target, effectivePartitions.length, i, schemaDiff, options);
|
|
85
|
-
await options?.onStepProgress?.("partitionEnd", effectivePartitions.length, i);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
await options?.onStepProgress?.("migrationEnd", 0, 0);
|
|
89
|
-
return migrated;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Generic per-partition migration loop.
|
|
93
|
-
* @param source Connector to read from (current schema, already bootstrapped).
|
|
94
|
-
* @param target Connector to write to (new schema, already bootstrapped).
|
|
95
|
-
* @param partitionTotal The total number of partitions to migrate, used for progress reporting.
|
|
96
|
-
* @param partitionIndex The index of the current partition being migrated, used for progress reporting.
|
|
97
|
-
* @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.
|
|
98
|
-
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
99
|
-
* @returns The number of entities successfully migrated.
|
|
100
|
-
*/
|
|
101
|
-
static async migratePartition(source, target, partitionTotal, partitionIndex, schemaDiff, options) {
|
|
102
|
-
const batchSize = options?.batchSize ?? 100;
|
|
103
|
-
let migrated = 0;
|
|
104
|
-
let cursor;
|
|
105
|
-
const totalEntities = await source.count();
|
|
106
|
-
await options?.onPartitionProgress?.(totalEntities, 0);
|
|
107
|
-
if (totalEntities > 0) {
|
|
108
|
-
do {
|
|
109
|
-
const page = await source.query(undefined, undefined, undefined, cursor, batchSize);
|
|
110
|
-
cursor = page.cursor;
|
|
111
|
-
if (Is.arrayValue(page.entities)) {
|
|
112
|
-
const transformedBatch = [];
|
|
113
|
-
for (const entity of page.entities) {
|
|
114
|
-
const transformedEntity = MigrationHelper.applyEntityTransform(entity, schemaDiff, options);
|
|
115
|
-
transformedBatch.push(transformedEntity);
|
|
116
|
-
}
|
|
117
|
-
await target.setBatch(transformedBatch);
|
|
118
|
-
migrated += transformedBatch.length;
|
|
119
|
-
}
|
|
120
|
-
await options?.onPartitionProgress?.(totalEntities, migrated);
|
|
121
|
-
} while (Is.stringValue(cursor));
|
|
122
|
-
}
|
|
123
|
-
return migrated;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Applies the entity transformation for migration, using the provided options and schema diff.
|
|
17
|
+
* Applies the entity transformation for a single diff, handling added, removed, and
|
|
18
|
+
* modified properties according to the provided schema diff and optional transform hook.
|
|
127
19
|
* @param entity The entity to transform.
|
|
128
20
|
* @param schemaDiff The schema diff between the old and new schemas.
|
|
129
|
-
* @param
|
|
21
|
+
* @param transformEntityProperty Optional per-property transform hook for object/array properties.
|
|
130
22
|
* @returns The transformed entity ready to be written to the new schema.
|
|
131
|
-
* @throws GeneralError if a transformation is required for an object or array property but no
|
|
23
|
+
* @throws GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
|
|
132
24
|
* @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
133
25
|
*/
|
|
134
|
-
static applyEntityTransform(entity, schemaDiff,
|
|
26
|
+
static applyEntityTransform(entity, schemaDiff, transformEntityProperty) {
|
|
135
27
|
const newEntity = {};
|
|
136
28
|
for (const property of schemaDiff.unchanged) {
|
|
137
29
|
ObjectHelper.propertySet(newEntity, property.property, ObjectHelper.propertyGet(entity, property.property));
|
|
@@ -140,20 +32,20 @@ export class MigrationHelper {
|
|
|
140
32
|
let defValue;
|
|
141
33
|
if (!(change.optional ?? false)) {
|
|
142
34
|
if (change.type === EntitySchemaPropertyType.Boolean) {
|
|
143
|
-
defValue = false;
|
|
35
|
+
defValue = change.defaultValue ?? false;
|
|
144
36
|
}
|
|
145
37
|
else if (change.type === EntitySchemaPropertyType.Number ||
|
|
146
38
|
change.type === EntitySchemaPropertyType.Integer) {
|
|
147
|
-
defValue = 0;
|
|
39
|
+
defValue = change.defaultValue ?? 0;
|
|
148
40
|
}
|
|
149
41
|
else if (change.type === EntitySchemaPropertyType.String) {
|
|
150
|
-
defValue = "";
|
|
42
|
+
defValue = change.defaultValue ?? "";
|
|
151
43
|
}
|
|
152
44
|
else if (change.type === EntitySchemaPropertyType.Array) {
|
|
153
|
-
defValue = [];
|
|
45
|
+
defValue = change.defaultValue ?? [];
|
|
154
46
|
}
|
|
155
47
|
else if (change.type === EntitySchemaPropertyType.Object) {
|
|
156
|
-
defValue = {};
|
|
48
|
+
defValue = change.defaultValue ?? {};
|
|
157
49
|
}
|
|
158
50
|
}
|
|
159
51
|
if (Is.notEmpty(defValue)) {
|
|
@@ -175,14 +67,14 @@ export class MigrationHelper {
|
|
|
175
67
|
}
|
|
176
68
|
else if (change.to.type === EntitySchemaPropertyType.Array ||
|
|
177
69
|
change.to.type === EntitySchemaPropertyType.Object) {
|
|
178
|
-
if (!Is.function(
|
|
70
|
+
if (!Is.function(transformEntityProperty)) {
|
|
179
71
|
throw new GeneralError(MigrationHelper.CLASS_NAME, "transformRequiredForProperty", {
|
|
180
72
|
from: change.from.property,
|
|
181
73
|
to: change.to.property,
|
|
182
74
|
type: change.from.type
|
|
183
75
|
});
|
|
184
76
|
}
|
|
185
|
-
newValue =
|
|
77
|
+
newValue = transformEntityProperty(change.from, change.to, currentValue);
|
|
186
78
|
}
|
|
187
79
|
if (newValue === undefined && !(change.to.optional ?? false)) {
|
|
188
80
|
throw new GeneralError(MigrationHelper.CLASS_NAME, "coercionProducedUndefined", {
|
|
@@ -195,15 +87,96 @@ export class MigrationHelper {
|
|
|
195
87
|
}
|
|
196
88
|
}
|
|
197
89
|
// Removed properties are simply dropped.
|
|
198
|
-
// for (const change of schemaDiff.removed) {
|
|
199
|
-
// }
|
|
200
90
|
return newEntity;
|
|
201
91
|
}
|
|
202
92
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
93
|
+
* Transforms a single entity through an ordered chain of fully-resolved migration steps.
|
|
94
|
+
* For each step the method diffs fromProperties against toProperties, then applies
|
|
95
|
+
* applyEntityTransform. Each step's output feeds the next step's input so that
|
|
96
|
+
* per-step transformEntityProperty hooks are honoured throughout the chain.
|
|
97
|
+
* @param entity The entity to transform (at the shape described by steps[0].fromProperties).
|
|
98
|
+
* @param steps Ordered, fully-resolved migration steps from stored version to current version.
|
|
99
|
+
* Each step's fromProperties and toProperties are resolved by the caller before invocation.
|
|
100
|
+
* @returns The entity transformed to the shape described by steps[last].toProperties.
|
|
101
|
+
*/
|
|
102
|
+
static applyEntityChain(entity, steps) {
|
|
103
|
+
let current = entity;
|
|
104
|
+
for (const step of steps) {
|
|
105
|
+
const diff = EntitySchemaDiffHelper.diff(step.fromProperties, step.toProperties, step.renames);
|
|
106
|
+
current = MigrationHelper.applyEntityTransform(current, diff, step.transformEntityProperty);
|
|
107
|
+
}
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Performs a chain migration in a single connector swap, regardless of how many version
|
|
112
|
+
* steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
113
|
+
* applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
114
|
+
* A chain of one step is equivalent to a traditional single-step migration.
|
|
115
|
+
* @param sourceConnector The connector holding data at the stored schema version.
|
|
116
|
+
* @param targetSchemaName The schema name for the current version (used to create the target connector).
|
|
117
|
+
* @param steps Ordered, fully-resolved migration steps from stored to current version.
|
|
118
|
+
* @param loggingComponentType An optional logging component type for connector startup.
|
|
119
|
+
* @param batchSize Number of entities to read and write per batch. Defaults to 100.
|
|
120
|
+
* @returns The finalized connector and the count of migrated entities.
|
|
121
|
+
*/
|
|
122
|
+
static async migrateWithChain(sourceConnector, targetSchemaName, steps, loggingComponentType, batchSize = 100) {
|
|
123
|
+
let targetConnector;
|
|
124
|
+
try {
|
|
125
|
+
targetConnector = await sourceConnector.createTargetConnector(targetSchemaName);
|
|
126
|
+
await MigrationHelper.startupConnector(sourceConnector, loggingComponentType);
|
|
127
|
+
await MigrationHelper.startupConnector(targetConnector, loggingComponentType);
|
|
128
|
+
let partitionContextIds = await sourceConnector.getPartitionContextIds();
|
|
129
|
+
if (!Is.arrayValue(partitionContextIds)) {
|
|
130
|
+
partitionContextIds ??= [];
|
|
131
|
+
partitionContextIds.push({});
|
|
132
|
+
}
|
|
133
|
+
let migrated = 0;
|
|
134
|
+
const effectivePartitions = partitionContextIds.length > 0 ? partitionContextIds : [{}];
|
|
135
|
+
const resolvedTarget = targetConnector;
|
|
136
|
+
for (const contextIds of effectivePartitions) {
|
|
137
|
+
await ContextIdStore.run(contextIds, async () => {
|
|
138
|
+
migrated += await MigrationHelper.migratePartitionWithChain(sourceConnector, resolvedTarget, steps, batchSize);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const finalConnector = await sourceConnector.finalizeMigration(targetConnector, undefined, loggingComponentType);
|
|
142
|
+
return { finalConnector, migrated };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
await sourceConnector.cleanupMigration(targetConnector, undefined, loggingComponentType);
|
|
146
|
+
throw new GeneralError(MigrationHelper.CLASS_NAME, "migrationFailed", undefined, error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reads all entities from one partition of the source connector, applies the migration
|
|
151
|
+
* chain to each entity, and writes the results to the target connector.
|
|
152
|
+
* @param source The connector to read from (already bootstrapped).
|
|
153
|
+
* @param target The connector to write to (already bootstrapped).
|
|
154
|
+
* @param steps Ordered, fully-resolved migration steps.
|
|
155
|
+
* @param batchSize Number of entities to read and write per batch.
|
|
156
|
+
* @returns The number of entities migrated.
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
static async migratePartitionWithChain(source, target, steps, batchSize) {
|
|
160
|
+
let migrated = 0;
|
|
161
|
+
let cursor;
|
|
162
|
+
const totalEntities = await source.count();
|
|
163
|
+
if (totalEntities > 0) {
|
|
164
|
+
do {
|
|
165
|
+
const page = await source.query(undefined, undefined, undefined, cursor, batchSize);
|
|
166
|
+
cursor = page.cursor;
|
|
167
|
+
if (Is.arrayValue(page.entities)) {
|
|
168
|
+
const transformedBatch = page.entities.map(entity => MigrationHelper.applyEntityChain(entity, steps));
|
|
169
|
+
await target.setBatch(transformedBatch);
|
|
170
|
+
migrated += transformedBatch.length;
|
|
171
|
+
}
|
|
172
|
+
} while (Is.stringValue(cursor));
|
|
173
|
+
}
|
|
174
|
+
return migrated;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Starts the connector by calling bootstrap and start if they are defined.
|
|
178
|
+
* @param connector The connector to start.
|
|
179
|
+
* @param loggingComponentType An optional logging component type.
|
|
207
180
|
* @internal
|
|
208
181
|
*/
|
|
209
182
|
static async startupConnector(connector, loggingComponentType) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrationHelper.js","sourceRoot":"","sources":["../../../src/helpers/migrationHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport { Coerce, GeneralError, Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tEntitySchemaDiffHelper,\n\tEntitySchemaPropertyType,\n\ttype IEntitySchemaDiff\n} from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntityStorageConnector } from \"../models/IEntityStorageConnector.js\";\nimport type { IEntityStorageMigrationConnector } from \"../models/IEntityStorageMigrationConnector.js\";\nimport type { IMigrationOptions } from \"../models/IMigrationOptions.js\";\n\n/**\n * Helper class for performing schema migrations between two connectors.\n */\nexport class MigrationHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<MigrationHelper>();\n\n\t/**\n\t * Performs a migration between two connectors, using the provided options and schema diff to control the migration behaviour.\n\t * @param sourceConnector The connector to migrate from to allow the migration helper to create the new connector and finalize the migration.\n\t * @param targetEntitySchemaName The name of the new entity schema.\n\t * @param renames An optional list of property renames to apply during migration.\n\t * @param options Options controlling migration behaviour.\n\t * @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.\n\t * @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.\n\t */\n\tpublic static async migrate<T, U>(\n\t\tsourceConnector: IEntityStorageMigrationConnector<T>,\n\t\ttargetEntitySchemaName: string,\n\t\trenames?: { from: string; to: string }[],\n\t\toptions?: IMigrationOptions<T, U>,\n\t\tloggingComponentType?: string\n\t): Promise<{\n\t\tfinalConnector?: IEntityStorageConnector<U>;\n\t\tmigrated: number;\n\t}> {\n\t\tlet targetConnector: IEntityStorageConnector<U> | undefined;\n\t\ttry {\n\t\t\t// We use the migration method to create the new connector as it will use a temporary storage location if necessary\n\t\t\ttargetConnector = await sourceConnector.createTargetConnector<U>(targetEntitySchemaName);\n\n\t\t\t// Startup both connectors to ensure they are ready for the migration, this will call bootstrap and start if they are defined.\n\t\t\tawait MigrationHelper.startupConnector<T>(sourceConnector, loggingComponentType);\n\t\t\tawait MigrationHelper.startupConnector<U>(targetConnector, loggingComponentType);\n\n\t\t\t// Get the unique partition context ids to run the migration for each partition.\n\t\t\tlet partitionContextIds = await sourceConnector.getPartitionContextIds();\n\n\t\t\t// If there are no partitions, we still want to run the migration once to handle the schema changes,\n\t\t\t// so we create a single empty context for the migration to run in.\n\t\t\tif (!Is.arrayValue(partitionContextIds)) {\n\t\t\t\tpartitionContextIds ??= [];\n\t\t\t\tpartitionContextIds.push({});\n\t\t\t}\n\n\t\t\t// Get the schemas\n\t\t\tconst sourceSchema = sourceConnector.getSchema();\n\t\t\tconst targetSchema = targetConnector.getSchema();\n\n\t\t\t// Get the schema diff between the source and target schemas.\n\t\t\tconst schemaDiff = EntitySchemaDiffHelper.diff<T, U>(\n\t\t\t\tsourceSchema.properties ?? [],\n\t\t\t\ttargetSchema.properties ?? [],\n\t\t\t\trenames\n\t\t\t);\n\n\t\t\t// Only perform a migration if the schemas have changed\n\t\t\tif (EntitySchemaDiffHelper.hasChanges(schemaDiff)) {\n\t\t\t\t// Perform the migration, which will read from the current store and write to the target connector's store.\n\t\t\t\tconst migrated = await MigrationHelper.migrateEntities<T, U>(\n\t\t\t\t\tsourceConnector,\n\t\t\t\t\ttargetConnector,\n\t\t\t\t\tpartitionContextIds,\n\t\t\t\t\tschemaDiff,\n\t\t\t\t\toptions\n\t\t\t\t);\n\n\t\t\t\t// The migration is now complete, finalize the migration which could entail\n\t\t\t\t// renaming of resources etc, determined by the implementation of finalizeMigration.\n\t\t\t\t// it also needs to stop and teardown any resources no longer in use after the migration,\n\t\t\t\t// such as the old connector and its underlying storage.\n\t\t\t\tconst finalConnector = await sourceConnector.finalizeMigration(\n\t\t\t\t\ttargetConnector,\n\t\t\t\t\toptions,\n\t\t\t\t\tloggingComponentType\n\t\t\t\t);\n\n\t\t\t\treturn {\n\t\t\t\t\tfinalConnector,\n\t\t\t\t\tmigrated\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tfinalConnector: undefined,\n\t\t\t\tmigrated: 0\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tawait sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);\n\n\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"migrationFailed\", undefined, error);\n\t\t}\n\t}\n\n\t/**\n\t * Generic per-partition migration loop.\n\t * @param source Connector to read from (current schema, already bootstrapped).\n\t * @param target Connector to write to (new schema, already bootstrapped).\n\t * @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.\n\t * @param schemaDiff The schema diff.\n\t * @param options Optional migration controls (batchSize, transformEntity, onProgress).\n\t * @returns The number of entities successfully migrated.\n\t */\n\tpublic static async migrateEntities<T = unknown, U = T>(\n\t\tsource: IEntityStorageMigrationConnector<T>,\n\t\ttarget: IEntityStorageConnector<U>,\n\t\tpartitionContextIds: IContextIds[],\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): Promise<number> {\n\t\tlet migrated = 0;\n\n\t\tawait options?.onStepProgress?.(\"migrationStart\", 0, 0);\n\n\t\tconst effectivePartitions: IContextIds[] =\n\t\t\tpartitionContextIds.length > 0 ? partitionContextIds : [{}];\n\n\t\tfor (let i = 0; i < effectivePartitions.length; i++) {\n\t\t\tawait ContextIdStore.run(effectivePartitions[i], async () => {\n\t\t\t\tawait options?.onStepProgress?.(\"partitionStart\", effectivePartitions.length, i);\n\n\t\t\t\tmigrated += await MigrationHelper.migratePartition(\n\t\t\t\t\tsource,\n\t\t\t\t\ttarget,\n\t\t\t\t\teffectivePartitions.length,\n\t\t\t\t\ti,\n\t\t\t\t\tschemaDiff,\n\t\t\t\t\toptions\n\t\t\t\t);\n\n\t\t\t\tawait options?.onStepProgress?.(\"partitionEnd\", effectivePartitions.length, i);\n\t\t\t});\n\t\t}\n\n\t\tawait options?.onStepProgress?.(\"migrationEnd\", 0, 0);\n\n\t\treturn migrated;\n\t}\n\n\t/**\n\t * Generic per-partition migration loop.\n\t * @param source Connector to read from (current schema, already bootstrapped).\n\t * @param target Connector to write to (new schema, already bootstrapped).\n\t * @param partitionTotal The total number of partitions to migrate, used for progress reporting.\n\t * @param partitionIndex The index of the current partition being migrated, used for progress reporting.\n\t * @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.\n\t * @param options Optional migration controls (batchSize, transformEntity, onProgress).\n\t * @returns The number of entities successfully migrated.\n\t */\n\tpublic static async migratePartition<T = unknown, U = unknown>(\n\t\tsource: IEntityStorageConnector<T>,\n\t\ttarget: IEntityStorageConnector<U>,\n\t\tpartitionTotal: number,\n\t\tpartitionIndex: number,\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): Promise<number> {\n\t\tconst batchSize = options?.batchSize ?? 100;\n\t\tlet migrated = 0;\n\t\tlet cursor: string | undefined;\n\n\t\tconst totalEntities = await source.count();\n\n\t\tawait options?.onPartitionProgress?.(totalEntities, 0);\n\n\t\tif (totalEntities > 0) {\n\t\t\tdo {\n\t\t\t\tconst page = await source.query(undefined, undefined, undefined, cursor, batchSize);\n\t\t\t\tcursor = page.cursor;\n\n\t\t\t\tif (Is.arrayValue(page.entities)) {\n\t\t\t\t\tconst transformedBatch: U[] = [];\n\n\t\t\t\t\tfor (const entity of page.entities) {\n\t\t\t\t\t\tconst transformedEntity = MigrationHelper.applyEntityTransform<T, U>(\n\t\t\t\t\t\t\tentity,\n\t\t\t\t\t\t\tschemaDiff,\n\t\t\t\t\t\t\toptions\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttransformedBatch.push(transformedEntity);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait target.setBatch(transformedBatch);\n\t\t\t\t\tmigrated += transformedBatch.length;\n\t\t\t\t}\n\n\t\t\t\tawait options?.onPartitionProgress?.(totalEntities, migrated);\n\t\t\t} while (Is.stringValue(cursor));\n\t\t}\n\n\t\treturn migrated;\n\t}\n\n\t/**\n\t * Applies the entity transformation for migration, using the provided options and schema diff.\n\t * @param entity The entity to transform.\n\t * @param schemaDiff The schema diff between the old and new schemas.\n\t * @param options The migration options.\n\t * @returns The transformed entity ready to be written to the new schema.\n\t * @throws GeneralError if a transformation is required for an object or array property but no `options.transformEntityProperty` function is provided.\n\t * @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.\n\t */\n\tpublic static applyEntityTransform<T = unknown, U = unknown>(\n\t\tentity: Partial<T>,\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): U {\n\t\tconst newEntity = {} as U;\n\n\t\tfor (const property of schemaDiff.unchanged) {\n\t\t\tObjectHelper.propertySet(\n\t\t\t\tnewEntity,\n\t\t\t\tproperty.property as string,\n\t\t\t\tObjectHelper.propertyGet(entity, property.property as string)\n\t\t\t);\n\t\t}\n\n\t\tfor (const change of schemaDiff.added) {\n\t\t\tlet defValue;\n\n\t\t\tif (!(change.optional ?? false)) {\n\t\t\t\tif (change.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\t\tdefValue = false;\n\t\t\t\t} else if (\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Number ||\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Integer\n\t\t\t\t) {\n\t\t\t\t\tdefValue = 0;\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.String) {\n\t\t\t\t\tdefValue = \"\";\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Array) {\n\t\t\t\t\tdefValue = [];\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Object) {\n\t\t\t\t\tdefValue = {};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(defValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.property as string, defValue);\n\t\t\t}\n\t\t}\n\n\t\tfor (const change of schemaDiff.modified) {\n\t\t\tconst currentValue = ObjectHelper.propertyGet(entity, change.from.property as string);\n\t\t\tlet newValue;\n\n\t\t\tif (change.to.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\tnewValue = Coerce.boolean(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Number ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Integer\n\t\t\t) {\n\t\t\t\tnewValue = Coerce.number(currentValue);\n\t\t\t} else if (change.to.type === EntitySchemaPropertyType.String) {\n\t\t\t\tnewValue = Coerce.string(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Array ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Object\n\t\t\t) {\n\t\t\t\tif (!Is.function(options?.transformEntityProperty)) {\n\t\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"transformRequiredForProperty\", {\n\t\t\t\t\t\tfrom: change.from.property,\n\t\t\t\t\t\tto: change.to.property,\n\t\t\t\t\t\ttype: change.from.type\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tnewValue = options.transformEntityProperty(change.from, change.to, currentValue);\n\t\t\t}\n\n\t\t\tif (newValue === undefined && !(change.to.optional ?? false)) {\n\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"coercionProducedUndefined\", {\n\t\t\t\t\tproperty: change.to.property,\n\t\t\t\t\ttype: change.to.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(newValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.to.property as string, newValue);\n\t\t\t}\n\t\t}\n\n\t\t// Removed properties are simply dropped.\n\t\t// for (const change of schemaDiff.removed) {\n\t\t// }\n\n\t\treturn newEntity;\n\t}\n\n\t/**\n\t * Startup the connector by calling bootstrap and start if they are defined.\n\t * @param connector The connector to startup.\n\t * @param loggingComponentType An optional logging component type to use for bootstrapping and starting the connector.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate static async startupConnector<T>(\n\t\tconnector: IEntityStorageConnector<T>,\n\t\tloggingComponentType: string | undefined\n\t): Promise<void> {\n\t\tconst bootstrap = connector.bootstrap?.bind(connector);\n\t\tif (Is.function(bootstrap)) {\n\t\t\tawait bootstrap(loggingComponentType);\n\t\t}\n\t\tconst start = connector.start?.bind(connector);\n\t\tif (Is.function(start)) {\n\t\t\tawait start(loggingComponentType);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"migrationHelper.js","sourceRoot":"","sources":["../../../src/helpers/migrationHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { ContextIdStore } from \"@twin.org/context\";\nimport { Coerce, GeneralError, Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tEntitySchemaDiffHelper,\n\tEntitySchemaPropertyType,\n\ttype IEntitySchemaDiff\n} from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntityStorageConnector } from \"../models/IEntityStorageConnector.js\";\nimport type { IEntityStorageMigrationConnector } from \"../models/IEntityStorageMigrationConnector.js\";\nimport type { IMigrationOptions } from \"../models/IMigrationOptions.js\";\nimport type { IResolvedMigrationStep } from \"../models/IResolvedMigrationStep.js\";\n\n/**\n * Helper class for performing entity schema migrations between two connectors.\n * The chain-based API (migrateWithChain / applyEntityChain) is the single migration\n * path: a chain of one step covers the same case as a traditional single-step migration.\n */\nexport class MigrationHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<MigrationHelper>();\n\n\t/**\n\t * Applies the entity transformation for a single diff, handling added, removed, and\n\t * modified properties according to the provided schema diff and optional transform hook.\n\t * @param entity The entity to transform.\n\t * @param schemaDiff The schema diff between the old and new schemas.\n\t * @param transformEntityProperty Optional per-property transform hook for object/array properties.\n\t * @returns The transformed entity ready to be written to the new schema.\n\t * @throws GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.\n\t * @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.\n\t */\n\tpublic static applyEntityTransform<T = unknown, U = unknown>(\n\t\tentity: Partial<T>,\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\ttransformEntityProperty?: IMigrationOptions<T, U>[\"transformEntityProperty\"]\n\t): U {\n\t\tconst newEntity = {} as U;\n\n\t\tfor (const property of schemaDiff.unchanged) {\n\t\t\tObjectHelper.propertySet(\n\t\t\t\tnewEntity,\n\t\t\t\tproperty.property as string,\n\t\t\t\tObjectHelper.propertyGet(entity, property.property as string)\n\t\t\t);\n\t\t}\n\n\t\tfor (const change of schemaDiff.added) {\n\t\t\tlet defValue;\n\n\t\t\tif (!(change.optional ?? false)) {\n\t\t\t\tif (change.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\t\tdefValue = change.defaultValue ?? false;\n\t\t\t\t} else if (\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Number ||\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Integer\n\t\t\t\t) {\n\t\t\t\t\tdefValue = change.defaultValue ?? 0;\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.String) {\n\t\t\t\t\tdefValue = change.defaultValue ?? \"\";\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Array) {\n\t\t\t\t\tdefValue = change.defaultValue ?? [];\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Object) {\n\t\t\t\t\tdefValue = change.defaultValue ?? {};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(defValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.property as string, defValue);\n\t\t\t}\n\t\t}\n\n\t\tfor (const change of schemaDiff.modified) {\n\t\t\tconst currentValue = ObjectHelper.propertyGet(entity, change.from.property as string);\n\t\t\tlet newValue;\n\n\t\t\tif (change.to.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\tnewValue = Coerce.boolean(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Number ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Integer\n\t\t\t) {\n\t\t\t\tnewValue = Coerce.number(currentValue);\n\t\t\t} else if (change.to.type === EntitySchemaPropertyType.String) {\n\t\t\t\tnewValue = Coerce.string(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Array ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Object\n\t\t\t) {\n\t\t\t\tif (!Is.function(transformEntityProperty)) {\n\t\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"transformRequiredForProperty\", {\n\t\t\t\t\t\tfrom: change.from.property,\n\t\t\t\t\t\tto: change.to.property,\n\t\t\t\t\t\ttype: change.from.type\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tnewValue = transformEntityProperty(change.from, change.to, currentValue);\n\t\t\t}\n\n\t\t\tif (newValue === undefined && !(change.to.optional ?? false)) {\n\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"coercionProducedUndefined\", {\n\t\t\t\t\tproperty: change.to.property,\n\t\t\t\t\ttype: change.to.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(newValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.to.property as string, newValue);\n\t\t\t}\n\t\t}\n\n\t\t// Removed properties are simply dropped.\n\n\t\treturn newEntity;\n\t}\n\n\t/**\n\t * Transforms a single entity through an ordered chain of fully-resolved migration steps.\n\t * For each step the method diffs fromProperties against toProperties, then applies\n\t * applyEntityTransform. Each step's output feeds the next step's input so that\n\t * per-step transformEntityProperty hooks are honoured throughout the chain.\n\t * @param entity The entity to transform (at the shape described by steps[0].fromProperties).\n\t * @param steps Ordered, fully-resolved migration steps from stored version to current version.\n\t * Each step's fromProperties and toProperties are resolved by the caller before invocation.\n\t * @returns The entity transformed to the shape described by steps[last].toProperties.\n\t */\n\tpublic static applyEntityChain(entity: unknown, steps: IResolvedMigrationStep[]): unknown {\n\t\tlet current: unknown = entity;\n\t\tfor (const step of steps) {\n\t\t\tconst diff = EntitySchemaDiffHelper.diff(\n\t\t\t\tstep.fromProperties,\n\t\t\t\tstep.toProperties,\n\t\t\t\tstep.renames\n\t\t\t);\n\t\t\tcurrent = MigrationHelper.applyEntityTransform(\n\t\t\t\tcurrent as Partial<unknown>,\n\t\t\t\tdiff,\n\t\t\t\tstep.transformEntityProperty\n\t\t\t);\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Performs a chain migration in a single connector swap, regardless of how many version\n\t * steps the chain spans. Creates one target connector, reads all source entities, applies\n\t * applyEntityChain to each, writes them to the target, then finalizes the migration.\n\t * A chain of one step is equivalent to a traditional single-step migration.\n\t * @param sourceConnector The connector holding data at the stored schema version.\n\t * @param targetSchemaName The schema name for the current version (used to create the target connector).\n\t * @param steps Ordered, fully-resolved migration steps from stored to current version.\n\t * @param loggingComponentType An optional logging component type for connector startup.\n\t * @param batchSize Number of entities to read and write per batch. Defaults to 100.\n\t * @returns The finalized connector and the count of migrated entities.\n\t */\n\tpublic static async migrateWithChain(\n\t\tsourceConnector: IEntityStorageMigrationConnector,\n\t\ttargetSchemaName: string,\n\t\tsteps: IResolvedMigrationStep[],\n\t\tloggingComponentType?: string,\n\t\tbatchSize = 100\n\t): Promise<{\n\t\tfinalConnector: IEntityStorageConnector;\n\t\tmigrated: number;\n\t}> {\n\t\tlet targetConnector: IEntityStorageConnector | undefined;\n\t\ttry {\n\t\t\ttargetConnector = await sourceConnector.createTargetConnector(targetSchemaName);\n\n\t\t\tawait MigrationHelper.startupConnector(sourceConnector, loggingComponentType);\n\t\t\tawait MigrationHelper.startupConnector(targetConnector, loggingComponentType);\n\n\t\t\tlet partitionContextIds = await sourceConnector.getPartitionContextIds();\n\t\t\tif (!Is.arrayValue(partitionContextIds)) {\n\t\t\t\tpartitionContextIds ??= [];\n\t\t\t\tpartitionContextIds.push({});\n\t\t\t}\n\n\t\t\tlet migrated = 0;\n\t\t\tconst effectivePartitions = partitionContextIds.length > 0 ? partitionContextIds : [{}];\n\n\t\t\tconst resolvedTarget = targetConnector;\n\t\t\tfor (const contextIds of effectivePartitions) {\n\t\t\t\tawait ContextIdStore.run(contextIds, async () => {\n\t\t\t\t\tmigrated += await MigrationHelper.migratePartitionWithChain(\n\t\t\t\t\t\tsourceConnector,\n\t\t\t\t\t\tresolvedTarget,\n\t\t\t\t\t\tsteps,\n\t\t\t\t\t\tbatchSize\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst finalConnector = await sourceConnector.finalizeMigration(\n\t\t\t\ttargetConnector,\n\t\t\t\tundefined,\n\t\t\t\tloggingComponentType\n\t\t\t);\n\n\t\t\treturn { finalConnector, migrated };\n\t\t} catch (error) {\n\t\t\tawait sourceConnector.cleanupMigration(targetConnector, undefined, loggingComponentType);\n\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"migrationFailed\", undefined, error);\n\t\t}\n\t}\n\n\t/**\n\t * Reads all entities from one partition of the source connector, applies the migration\n\t * chain to each entity, and writes the results to the target connector.\n\t * @param source The connector to read from (already bootstrapped).\n\t * @param target The connector to write to (already bootstrapped).\n\t * @param steps Ordered, fully-resolved migration steps.\n\t * @param batchSize Number of entities to read and write per batch.\n\t * @returns The number of entities migrated.\n\t * @internal\n\t */\n\tprivate static async migratePartitionWithChain(\n\t\tsource: IEntityStorageMigrationConnector,\n\t\ttarget: IEntityStorageConnector,\n\t\tsteps: IResolvedMigrationStep[],\n\t\tbatchSize: number\n\t): Promise<number> {\n\t\tlet migrated = 0;\n\t\tlet cursor: string | undefined;\n\t\tconst totalEntities = await source.count();\n\n\t\tif (totalEntities > 0) {\n\t\t\tdo {\n\t\t\t\tconst page = await source.query(undefined, undefined, undefined, cursor, batchSize);\n\t\t\t\tcursor = page.cursor;\n\n\t\t\t\tif (Is.arrayValue(page.entities)) {\n\t\t\t\t\tconst transformedBatch: unknown[] = page.entities.map(entity =>\n\t\t\t\t\t\tMigrationHelper.applyEntityChain(entity, steps)\n\t\t\t\t\t);\n\t\t\t\t\tawait target.setBatch(transformedBatch);\n\t\t\t\t\tmigrated += transformedBatch.length;\n\t\t\t\t}\n\t\t\t} while (Is.stringValue(cursor));\n\t\t}\n\n\t\treturn migrated;\n\t}\n\n\t/**\n\t * Starts the connector by calling bootstrap and start if they are defined.\n\t * @param connector The connector to start.\n\t * @param loggingComponentType An optional logging component type.\n\t * @internal\n\t */\n\tprivate static async startupConnector<T>(\n\t\tconnector: IEntityStorageConnector<T>,\n\t\tloggingComponentType: string | undefined\n\t): Promise<void> {\n\t\tconst bootstrap = connector.bootstrap?.bind(connector);\n\t\tif (Is.function(bootstrap)) {\n\t\t\tawait bootstrap(loggingComponentType);\n\t\t}\n\t\tconst start = connector.start?.bind(connector);\n\t\tif (Is.function(start)) {\n\t\t\tawait start(loggingComponentType);\n\t\t}\n\t}\n}\n"]}
|
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// Copyright
|
|
1
|
+
// Copyright 2026 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
export * from "./factories/entityStorageConnectorFactory.js";
|
|
4
|
+
export * from "./factories/schemaMigrationFactory.js";
|
|
4
5
|
export * from "./helpers/entityStorageHelper.js";
|
|
5
6
|
export * from "./helpers/migrationHelper.js";
|
|
6
7
|
export * from "./models/api/IEntityStorageCountRequest.js";
|
|
@@ -18,4 +19,6 @@ export * from "./models/IEntityStorageComponent.js";
|
|
|
18
19
|
export * from "./models/IEntityStorageConnector.js";
|
|
19
20
|
export * from "./models/IEntityStorageMigrationConnector.js";
|
|
20
21
|
export * from "./models/IMigrationOptions.js";
|
|
22
|
+
export * from "./models/IResolvedMigrationStep.js";
|
|
23
|
+
export * from "./models/ISchemaMigration.js";
|
|
21
24
|
//# sourceMappingURL=index.js.map
|
package/dist/es/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+BAA+B,CAAC","sourcesContent":["// Copyright
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AACnD,cAAc,8BAA8B,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./factories/entityStorageConnectorFactory.js\";\nexport * from \"./factories/schemaMigrationFactory.js\";\nexport * from \"./helpers/entityStorageHelper.js\";\nexport * from \"./helpers/migrationHelper.js\";\nexport * from \"./models/api/IEntityStorageCountRequest.js\";\nexport * from \"./models/api/IEntityStorageCountResponse.js\";\nexport * from \"./models/api/IEntityStorageEmptyRequest.js\";\nexport * from \"./models/api/IEntityStorageGetRequest.js\";\nexport * from \"./models/api/IEntityStorageGetResponse.js\";\nexport * from \"./models/api/IEntityStorageListRequest.js\";\nexport * from \"./models/api/IEntityStorageListResponse.js\";\nexport * from \"./models/api/IEntityStorageRemoveBatchRequest.js\";\nexport * from \"./models/api/IEntityStorageRemoveRequest.js\";\nexport * from \"./models/api/IEntityStorageSetBatchRequest.js\";\nexport * from \"./models/api/IEntityStorageSetRequest.js\";\nexport * from \"./models/IEntityStorageComponent.js\";\nexport * from \"./models/IEntityStorageConnector.js\";\nexport * from \"./models/IEntityStorageMigrationConnector.js\";\nexport * from \"./models/IMigrationOptions.js\";\nexport * from \"./models/IResolvedMigrationStep.js\";\nexport * from \"./models/ISchemaMigration.js\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IResolvedMigrationStep.js","sourceRoot":"","sources":["../../../src/models/IResolvedMigrationStep.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IEntitySchemaProperty } from \"@twin.org/entity\";\nimport type { IMigrationOptions } from \"./IMigrationOptions.js\";\n\n/**\n * A fully-resolved single migration step used by MigrationHelper.\n * The SchemaVersionService builds these by looking up versioned schema classes\n * from EntitySchemaFactory (e.g. MyEntityV0, MyEntityV1) before invoking the helper,\n * keeping factory knowledge out of the helper itself.\n * @template T The entity type. Defaults to `unknown`. Use a concrete entity type\n * when the step's source and target schemas are known at the call site.\n */\nexport interface IResolvedMigrationStep<T = unknown, U = unknown> {\n\t/**\n\t * The property list of the entity at the start of this step (the \"old\" shape).\n\t * Sourced from the versioned schema class registered in EntitySchemaFactory,\n\t * e.g. EntitySchemaFactory.get(\"MyEntityV0\").properties.\n\t */\n\tfromProperties: IEntitySchemaProperty<T>[];\n\n\t/**\n\t * The property list of the entity at the end of this step (the \"new\" shape).\n\t * For the final step this is the live current schema's properties.\n\t */\n\ttoProperties: IEntitySchemaProperty<U>[];\n\n\t/**\n\t * Optional property renames for this step, forwarded to EntitySchemaDiffHelper.diff.\n\t */\n\trenames?: { from: string; to: string }[];\n\n\t/**\n\t * Optional per-property transformer for object/array properties that the structural\n\t * diff cannot handle automatically. Sourced from an ISchemaMigration override when\n\t * one is registered in SchemaMigrationFactory for this step.\n\t */\n\ttransformEntityProperty?: IMigrationOptions<T, U>[\"transformEntityProperty\"];\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ISchemaMigration.js","sourceRoot":"","sources":["../../../src/models/ISchemaMigration.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IMigrationOptions } from \"./IMigrationOptions.js\";\n\n/**\n * Optional per-step override for a single version-to-version migration.\n * Only register an entry in SchemaMigrationFactory when a step requires property\n * renames or a custom object/array transform. For purely structural changes\n * (add/remove/type-change fields) no entry is needed — the runner diffs the two\n * versioned schema classes (e.g. MyEntityV0 vs MyEntityV1) from EntitySchemaFactory\n * automatically.\n *\n * Register under the key \"<BaseSchemaName>_<fromVersion>_<toVersion>\"\n * e.g. \"MyEntity_0_1\" for the step that migrates from version 0 to version 1.\n * The key itself encodes the version pair; no version field is needed on the object.\n */\nexport interface ISchemaMigration<T = unknown, U = unknown> {\n\t/**\n\t * Optional property renames to apply during this step.\n\t */\n\trenames?: { from: string; to: string }[];\n\n\t/**\n\t * Optional per-property transformer for object/array properties that cannot be\n\t * automatically coerced. T is the source entity type, U is the target entity type.\n\t */\n\ttransformEntityProperty?: IMigrationOptions<T, U>[\"transformEntityProperty\"];\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Factory } from "@twin.org/core";
|
|
2
|
+
import type { ISchemaMigration } from "../models/ISchemaMigration.js";
|
|
3
|
+
/**
|
|
4
|
+
* Factory for optional per-step migration overrides.
|
|
5
|
+
*
|
|
6
|
+
* Only register an entry when a version step requires property renames or a custom
|
|
7
|
+
* transform hook. For purely structural changes (add/remove/type-change) the
|
|
8
|
+
* SchemaVersionService diffs the two versioned schema classes automatically
|
|
9
|
+
* without needing any factory entry.
|
|
10
|
+
*
|
|
11
|
+
* Keys follow the convention "<BaseSchemaName>_<fromVersion>_<toVersion>",
|
|
12
|
+
* for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SchemaMigrationFactory: Factory<ISchemaMigration<unknown, unknown>>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type IContextIds } from "@twin.org/context";
|
|
2
1
|
import { type IEntitySchemaDiff } from "@twin.org/entity";
|
|
3
2
|
import type { IEntityStorageConnector } from "../models/IEntityStorageConnector.js";
|
|
4
3
|
import type { IEntityStorageMigrationConnector } from "../models/IEntityStorageMigrationConnector.js";
|
|
5
4
|
import type { IMigrationOptions } from "../models/IMigrationOptions.js";
|
|
5
|
+
import type { IResolvedMigrationStep } from "../models/IResolvedMigrationStep.js";
|
|
6
6
|
/**
|
|
7
|
-
* Helper class for performing schema migrations between two connectors.
|
|
7
|
+
* Helper class for performing entity schema migrations between two connectors.
|
|
8
|
+
* The chain-based API (migrateWithChain / applyEntityChain) is the single migration
|
|
9
|
+
* path: a chain of one step covers the same case as a traditional single-step migration.
|
|
8
10
|
*/
|
|
9
11
|
export declare class MigrationHelper {
|
|
10
12
|
/**
|
|
@@ -12,50 +14,41 @@ export declare class MigrationHelper {
|
|
|
12
14
|
*/
|
|
13
15
|
static readonly CLASS_NAME: string;
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param targetEntitySchemaName The name of the new entity schema.
|
|
18
|
-
* @param renames An optional list of property renames to apply during migration.
|
|
19
|
-
* @param options Options controlling migration behaviour.
|
|
20
|
-
* @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.
|
|
21
|
-
* @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.
|
|
22
|
-
*/
|
|
23
|
-
static migrate<T, U>(sourceConnector: IEntityStorageMigrationConnector<T>, targetEntitySchemaName: string, renames?: {
|
|
24
|
-
from: string;
|
|
25
|
-
to: string;
|
|
26
|
-
}[], options?: IMigrationOptions<T, U>, loggingComponentType?: string): Promise<{
|
|
27
|
-
finalConnector?: IEntityStorageConnector<U>;
|
|
28
|
-
migrated: number;
|
|
29
|
-
}>;
|
|
30
|
-
/**
|
|
31
|
-
* Generic per-partition migration loop.
|
|
32
|
-
* @param source Connector to read from (current schema, already bootstrapped).
|
|
33
|
-
* @param target Connector to write to (new schema, already bootstrapped).
|
|
34
|
-
* @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.
|
|
35
|
-
* @param schemaDiff The schema diff.
|
|
36
|
-
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
37
|
-
* @returns The number of entities successfully migrated.
|
|
38
|
-
*/
|
|
39
|
-
static migrateEntities<T = unknown, U = T>(source: IEntityStorageMigrationConnector<T>, target: IEntityStorageConnector<U>, partitionContextIds: IContextIds[], schemaDiff: IEntitySchemaDiff<T, U>, options?: IMigrationOptions<T, U>): Promise<number>;
|
|
40
|
-
/**
|
|
41
|
-
* Generic per-partition migration loop.
|
|
42
|
-
* @param source Connector to read from (current schema, already bootstrapped).
|
|
43
|
-
* @param target Connector to write to (new schema, already bootstrapped).
|
|
44
|
-
* @param partitionTotal The total number of partitions to migrate, used for progress reporting.
|
|
45
|
-
* @param partitionIndex The index of the current partition being migrated, used for progress reporting.
|
|
46
|
-
* @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.
|
|
47
|
-
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
48
|
-
* @returns The number of entities successfully migrated.
|
|
49
|
-
*/
|
|
50
|
-
static migratePartition<T = unknown, U = unknown>(source: IEntityStorageConnector<T>, target: IEntityStorageConnector<U>, partitionTotal: number, partitionIndex: number, schemaDiff: IEntitySchemaDiff<T, U>, options?: IMigrationOptions<T, U>): Promise<number>;
|
|
51
|
-
/**
|
|
52
|
-
* Applies the entity transformation for migration, using the provided options and schema diff.
|
|
17
|
+
* Applies the entity transformation for a single diff, handling added, removed, and
|
|
18
|
+
* modified properties according to the provided schema diff and optional transform hook.
|
|
53
19
|
* @param entity The entity to transform.
|
|
54
20
|
* @param schemaDiff The schema diff between the old and new schemas.
|
|
55
|
-
* @param
|
|
21
|
+
* @param transformEntityProperty Optional per-property transform hook for object/array properties.
|
|
56
22
|
* @returns The transformed entity ready to be written to the new schema.
|
|
57
|
-
* @throws GeneralError if a transformation is required for an object or array property but no
|
|
23
|
+
* @throws GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
|
|
58
24
|
* @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
59
25
|
*/
|
|
60
|
-
static applyEntityTransform<T = unknown, U = unknown>(entity: Partial<T>, schemaDiff: IEntitySchemaDiff<T, U>,
|
|
26
|
+
static applyEntityTransform<T = unknown, U = unknown>(entity: Partial<T>, schemaDiff: IEntitySchemaDiff<T, U>, transformEntityProperty?: IMigrationOptions<T, U>["transformEntityProperty"]): U;
|
|
27
|
+
/**
|
|
28
|
+
* Transforms a single entity through an ordered chain of fully-resolved migration steps.
|
|
29
|
+
* For each step the method diffs fromProperties against toProperties, then applies
|
|
30
|
+
* applyEntityTransform. Each step's output feeds the next step's input so that
|
|
31
|
+
* per-step transformEntityProperty hooks are honoured throughout the chain.
|
|
32
|
+
* @param entity The entity to transform (at the shape described by steps[0].fromProperties).
|
|
33
|
+
* @param steps Ordered, fully-resolved migration steps from stored version to current version.
|
|
34
|
+
* Each step's fromProperties and toProperties are resolved by the caller before invocation.
|
|
35
|
+
* @returns The entity transformed to the shape described by steps[last].toProperties.
|
|
36
|
+
*/
|
|
37
|
+
static applyEntityChain(entity: unknown, steps: IResolvedMigrationStep[]): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Performs a chain migration in a single connector swap, regardless of how many version
|
|
40
|
+
* steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
41
|
+
* applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
42
|
+
* A chain of one step is equivalent to a traditional single-step migration.
|
|
43
|
+
* @param sourceConnector The connector holding data at the stored schema version.
|
|
44
|
+
* @param targetSchemaName The schema name for the current version (used to create the target connector).
|
|
45
|
+
* @param steps Ordered, fully-resolved migration steps from stored to current version.
|
|
46
|
+
* @param loggingComponentType An optional logging component type for connector startup.
|
|
47
|
+
* @param batchSize Number of entities to read and write per batch. Defaults to 100.
|
|
48
|
+
* @returns The finalized connector and the count of migrated entities.
|
|
49
|
+
*/
|
|
50
|
+
static migrateWithChain(sourceConnector: IEntityStorageMigrationConnector, targetSchemaName: string, steps: IResolvedMigrationStep[], loggingComponentType?: string, batchSize?: number): Promise<{
|
|
51
|
+
finalConnector: IEntityStorageConnector;
|
|
52
|
+
migrated: number;
|
|
53
|
+
}>;
|
|
61
54
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./factories/entityStorageConnectorFactory.js";
|
|
2
|
+
export * from "./factories/schemaMigrationFactory.js";
|
|
2
3
|
export * from "./helpers/entityStorageHelper.js";
|
|
3
4
|
export * from "./helpers/migrationHelper.js";
|
|
4
5
|
export * from "./models/api/IEntityStorageCountRequest.js";
|
|
@@ -16,3 +17,5 @@ export * from "./models/IEntityStorageComponent.js";
|
|
|
16
17
|
export * from "./models/IEntityStorageConnector.js";
|
|
17
18
|
export * from "./models/IEntityStorageMigrationConnector.js";
|
|
18
19
|
export * from "./models/IMigrationOptions.js";
|
|
20
|
+
export * from "./models/IResolvedMigrationStep.js";
|
|
21
|
+
export * from "./models/ISchemaMigration.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IEntitySchemaProperty } from "@twin.org/entity";
|
|
2
|
+
import type { IMigrationOptions } from "./IMigrationOptions.js";
|
|
3
|
+
/**
|
|
4
|
+
* A fully-resolved single migration step used by MigrationHelper.
|
|
5
|
+
* The SchemaVersionService builds these by looking up versioned schema classes
|
|
6
|
+
* from EntitySchemaFactory (e.g. MyEntityV0, MyEntityV1) before invoking the helper,
|
|
7
|
+
* keeping factory knowledge out of the helper itself.
|
|
8
|
+
* @template T The entity type. Defaults to `unknown`. Use a concrete entity type
|
|
9
|
+
* when the step's source and target schemas are known at the call site.
|
|
10
|
+
*/
|
|
11
|
+
export interface IResolvedMigrationStep<T = unknown, U = unknown> {
|
|
12
|
+
/**
|
|
13
|
+
* The property list of the entity at the start of this step (the "old" shape).
|
|
14
|
+
* Sourced from the versioned schema class registered in EntitySchemaFactory,
|
|
15
|
+
* e.g. EntitySchemaFactory.get("MyEntityV0").properties.
|
|
16
|
+
*/
|
|
17
|
+
fromProperties: IEntitySchemaProperty<T>[];
|
|
18
|
+
/**
|
|
19
|
+
* The property list of the entity at the end of this step (the "new" shape).
|
|
20
|
+
* For the final step this is the live current schema's properties.
|
|
21
|
+
*/
|
|
22
|
+
toProperties: IEntitySchemaProperty<U>[];
|
|
23
|
+
/**
|
|
24
|
+
* Optional property renames for this step, forwarded to EntitySchemaDiffHelper.diff.
|
|
25
|
+
*/
|
|
26
|
+
renames?: {
|
|
27
|
+
from: string;
|
|
28
|
+
to: string;
|
|
29
|
+
}[];
|
|
30
|
+
/**
|
|
31
|
+
* Optional per-property transformer for object/array properties that the structural
|
|
32
|
+
* diff cannot handle automatically. Sourced from an ISchemaMigration override when
|
|
33
|
+
* one is registered in SchemaMigrationFactory for this step.
|
|
34
|
+
*/
|
|
35
|
+
transformEntityProperty?: IMigrationOptions<T, U>["transformEntityProperty"];
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IMigrationOptions } from "./IMigrationOptions.js";
|
|
2
|
+
/**
|
|
3
|
+
* Optional per-step override for a single version-to-version migration.
|
|
4
|
+
* Only register an entry in SchemaMigrationFactory when a step requires property
|
|
5
|
+
* renames or a custom object/array transform. For purely structural changes
|
|
6
|
+
* (add/remove/type-change fields) no entry is needed — the runner diffs the two
|
|
7
|
+
* versioned schema classes (e.g. MyEntityV0 vs MyEntityV1) from EntitySchemaFactory
|
|
8
|
+
* automatically.
|
|
9
|
+
*
|
|
10
|
+
* Register under the key "<BaseSchemaName>_<fromVersion>_<toVersion>"
|
|
11
|
+
* e.g. "MyEntity_0_1" for the step that migrates from version 0 to version 1.
|
|
12
|
+
* The key itself encodes the version pair; no version field is needed on the object.
|
|
13
|
+
*/
|
|
14
|
+
export interface ISchemaMigration<T = unknown, U = unknown> {
|
|
15
|
+
/**
|
|
16
|
+
* Optional property renames to apply during this step.
|
|
17
|
+
*/
|
|
18
|
+
renames?: {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
}[];
|
|
22
|
+
/**
|
|
23
|
+
* Optional per-property transformer for object/array properties that cannot be
|
|
24
|
+
* automatically coerced. T is the source entity type, U is the target entity type.
|
|
25
|
+
*/
|
|
26
|
+
transformEntityProperty?: IMigrationOptions<T, U>["transformEntityProperty"];
|
|
27
|
+
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.22](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.21...entity-storage-models-v0.0.3-next.22) (2026-06-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add ISchemaMigration chain, SchemaVersionMigrator runner and version store ([#110](https://github.com/iotaledger/twin-entity-storage/issues/110)) ([2dac924](https://github.com/iotaledger/twin-entity-storage/commit/2dac9244a752cb58304d1649ff03c3a2469783dd))
|
|
9
|
+
|
|
10
|
+
## [0.0.3-next.21](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.20...entity-storage-models-v0.0.3-next.21) (2026-06-01)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Miscellaneous Chores
|
|
14
|
+
|
|
15
|
+
* **entity-storage-models:** Synchronize repo versions
|
|
16
|
+
|
|
3
17
|
## [0.0.3-next.20](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.19...entity-storage-models-v0.0.3-next.20) (2026-06-01)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Class: MigrationHelper
|
|
2
2
|
|
|
3
|
-
Helper class for performing schema migrations between two connectors.
|
|
3
|
+
Helper class for performing entity schema migrations between two connectors.
|
|
4
|
+
The chain-based API (migrateWithChain / applyEntityChain) is the single migration
|
|
5
|
+
path: a chain of one step covers the same case as a traditional single-step migration.
|
|
4
6
|
|
|
5
7
|
## Constructors
|
|
6
8
|
|
|
@@ -22,67 +24,12 @@ Runtime name for the class.
|
|
|
22
24
|
|
|
23
25
|
## Methods
|
|
24
26
|
|
|
25
|
-
###
|
|
26
|
-
|
|
27
|
-
> `static` **migrate**\<`T`, `U`\>(`sourceConnector`, `targetEntitySchemaName`, `renames?`, `options?`, `loggingComponentType?`): `Promise`\<\{ `finalConnector?`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)\<`U`\>; `migrated`: `number`; \}\>
|
|
28
|
-
|
|
29
|
-
Performs a migration between two connectors, using the provided options and schema diff to control the migration behaviour.
|
|
30
|
-
|
|
31
|
-
#### Type Parameters
|
|
32
|
-
|
|
33
|
-
##### T
|
|
34
|
-
|
|
35
|
-
`T`
|
|
36
|
-
|
|
37
|
-
##### U
|
|
38
|
-
|
|
39
|
-
`U`
|
|
40
|
-
|
|
41
|
-
#### Parameters
|
|
42
|
-
|
|
43
|
-
##### sourceConnector
|
|
44
|
-
|
|
45
|
-
[`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)\<`T`\>
|
|
46
|
-
|
|
47
|
-
The connector to migrate from to allow the migration helper to create the new connector and finalize the migration.
|
|
48
|
-
|
|
49
|
-
##### targetEntitySchemaName
|
|
50
|
-
|
|
51
|
-
`string`
|
|
52
|
-
|
|
53
|
-
The name of the new entity schema.
|
|
54
|
-
|
|
55
|
-
##### renames?
|
|
56
|
-
|
|
57
|
-
`object`[]
|
|
58
|
-
|
|
59
|
-
An optional list of property renames to apply during migration.
|
|
60
|
-
|
|
61
|
-
##### options?
|
|
62
|
-
|
|
63
|
-
[`IMigrationOptions`](../interfaces/IMigrationOptions.md)\<`T`, `U`\>
|
|
64
|
-
|
|
65
|
-
Options controlling migration behaviour.
|
|
66
|
-
|
|
67
|
-
##### loggingComponentType?
|
|
68
|
-
|
|
69
|
-
`string`
|
|
70
|
-
|
|
71
|
-
An optional logging component type to use for bootstrapping and starting connectors if necessary.
|
|
72
|
-
|
|
73
|
-
#### Returns
|
|
74
|
-
|
|
75
|
-
`Promise`\<\{ `finalConnector?`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)\<`U`\>; `migrated`: `number`; \}\>
|
|
76
|
-
|
|
77
|
-
The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.
|
|
78
|
-
|
|
79
|
-
***
|
|
80
|
-
|
|
81
|
-
### migrateEntities() {#migrateentities}
|
|
27
|
+
### applyEntityTransform() {#applyentitytransform}
|
|
82
28
|
|
|
83
|
-
> `static` **
|
|
29
|
+
> `static` **applyEntityTransform**\<`T`, `U`\>(`entity`, `schemaDiff`, `transformEntityProperty?`): `U`
|
|
84
30
|
|
|
85
|
-
|
|
31
|
+
Applies the entity transformation for a single diff, handling added, removed, and
|
|
32
|
+
modified properties according to the provided schema diff and optional transform hook.
|
|
86
33
|
|
|
87
34
|
#### Type Parameters
|
|
88
35
|
|
|
@@ -92,156 +39,119 @@ Generic per-partition migration loop.
|
|
|
92
39
|
|
|
93
40
|
##### U
|
|
94
41
|
|
|
95
|
-
`U` = `
|
|
42
|
+
`U` = `unknown`
|
|
96
43
|
|
|
97
44
|
#### Parameters
|
|
98
45
|
|
|
99
|
-
#####
|
|
100
|
-
|
|
101
|
-
[`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)\<`T`\>
|
|
102
|
-
|
|
103
|
-
Connector to read from (current schema, already bootstrapped).
|
|
104
|
-
|
|
105
|
-
##### target
|
|
106
|
-
|
|
107
|
-
[`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)\<`U`\>
|
|
108
|
-
|
|
109
|
-
Connector to write to (new schema, already bootstrapped).
|
|
110
|
-
|
|
111
|
-
##### partitionContextIds
|
|
46
|
+
##### entity
|
|
112
47
|
|
|
113
|
-
`
|
|
48
|
+
`Partial`\<`T`\>
|
|
114
49
|
|
|
115
|
-
The
|
|
50
|
+
The entity to transform.
|
|
116
51
|
|
|
117
52
|
##### schemaDiff
|
|
118
53
|
|
|
119
54
|
`IEntitySchemaDiff`\<`T`, `U`\>
|
|
120
55
|
|
|
121
|
-
The schema diff.
|
|
56
|
+
The schema diff between the old and new schemas.
|
|
122
57
|
|
|
123
|
-
#####
|
|
58
|
+
##### transformEntityProperty?
|
|
124
59
|
|
|
125
|
-
|
|
60
|
+
(`schema1Property`, `schemaProperty2`, `value`) => `unknown`
|
|
126
61
|
|
|
127
|
-
Optional
|
|
62
|
+
Optional per-property transform hook for object/array properties.
|
|
128
63
|
|
|
129
64
|
#### Returns
|
|
130
65
|
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
The number of entities successfully migrated.
|
|
66
|
+
`U`
|
|
134
67
|
|
|
135
|
-
|
|
68
|
+
The transformed entity ready to be written to the new schema.
|
|
136
69
|
|
|
137
|
-
|
|
70
|
+
#### Throws
|
|
138
71
|
|
|
139
|
-
|
|
72
|
+
GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
|
|
140
73
|
|
|
141
|
-
|
|
74
|
+
#### Throws
|
|
142
75
|
|
|
143
|
-
|
|
76
|
+
GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
144
77
|
|
|
145
|
-
|
|
78
|
+
***
|
|
146
79
|
|
|
147
|
-
|
|
80
|
+
### applyEntityChain() {#applyentitychain}
|
|
148
81
|
|
|
149
|
-
|
|
82
|
+
> `static` **applyEntityChain**(`entity`, `steps`): `unknown`
|
|
150
83
|
|
|
151
|
-
|
|
84
|
+
Transforms a single entity through an ordered chain of fully-resolved migration steps.
|
|
85
|
+
For each step the method diffs fromProperties against toProperties, then applies
|
|
86
|
+
applyEntityTransform. Each step's output feeds the next step's input so that
|
|
87
|
+
per-step transformEntityProperty hooks are honoured throughout the chain.
|
|
152
88
|
|
|
153
89
|
#### Parameters
|
|
154
90
|
|
|
155
|
-
#####
|
|
156
|
-
|
|
157
|
-
[`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)\<`T`\>
|
|
158
|
-
|
|
159
|
-
Connector to read from (current schema, already bootstrapped).
|
|
160
|
-
|
|
161
|
-
##### target
|
|
162
|
-
|
|
163
|
-
[`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)\<`U`\>
|
|
164
|
-
|
|
165
|
-
Connector to write to (new schema, already bootstrapped).
|
|
166
|
-
|
|
167
|
-
##### partitionTotal
|
|
168
|
-
|
|
169
|
-
`number`
|
|
170
|
-
|
|
171
|
-
The total number of partitions to migrate, used for progress reporting.
|
|
172
|
-
|
|
173
|
-
##### partitionIndex
|
|
174
|
-
|
|
175
|
-
`number`
|
|
176
|
-
|
|
177
|
-
The index of the current partition being migrated, used for progress reporting.
|
|
178
|
-
|
|
179
|
-
##### schemaDiff
|
|
91
|
+
##### entity
|
|
180
92
|
|
|
181
|
-
`
|
|
93
|
+
`unknown`
|
|
182
94
|
|
|
183
|
-
|
|
95
|
+
The entity to transform (at the shape described by steps[0].fromProperties).
|
|
184
96
|
|
|
185
|
-
#####
|
|
97
|
+
##### steps
|
|
186
98
|
|
|
187
|
-
[`
|
|
99
|
+
[`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
|
|
188
100
|
|
|
189
|
-
|
|
101
|
+
Ordered, fully-resolved migration steps from stored version to current version.
|
|
102
|
+
Each step's fromProperties and toProperties are resolved by the caller before invocation.
|
|
190
103
|
|
|
191
104
|
#### Returns
|
|
192
105
|
|
|
193
|
-
`
|
|
106
|
+
`unknown`
|
|
194
107
|
|
|
195
|
-
The
|
|
108
|
+
The entity transformed to the shape described by steps[last].toProperties.
|
|
196
109
|
|
|
197
110
|
***
|
|
198
111
|
|
|
199
|
-
###
|
|
112
|
+
### migrateWithChain() {#migratewithchain}
|
|
200
113
|
|
|
201
|
-
> `static` **
|
|
114
|
+
> `static` **migrateWithChain**(`sourceConnector`, `targetSchemaName`, `steps`, `loggingComponentType?`, `batchSize?`): `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
|
|
202
115
|
|
|
203
|
-
|
|
116
|
+
Performs a chain migration in a single connector swap, regardless of how many version
|
|
117
|
+
steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
118
|
+
applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
119
|
+
A chain of one step is equivalent to a traditional single-step migration.
|
|
204
120
|
|
|
205
|
-
####
|
|
206
|
-
|
|
207
|
-
##### T
|
|
208
|
-
|
|
209
|
-
`T` = `unknown`
|
|
210
|
-
|
|
211
|
-
##### U
|
|
121
|
+
#### Parameters
|
|
212
122
|
|
|
213
|
-
|
|
123
|
+
##### sourceConnector
|
|
214
124
|
|
|
215
|
-
|
|
125
|
+
[`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)
|
|
216
126
|
|
|
217
|
-
|
|
127
|
+
The connector holding data at the stored schema version.
|
|
218
128
|
|
|
219
|
-
|
|
129
|
+
##### targetSchemaName
|
|
220
130
|
|
|
221
|
-
|
|
131
|
+
`string`
|
|
222
132
|
|
|
223
|
-
|
|
133
|
+
The schema name for the current version (used to create the target connector).
|
|
224
134
|
|
|
225
|
-
|
|
135
|
+
##### steps
|
|
226
136
|
|
|
227
|
-
|
|
137
|
+
[`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
|
|
228
138
|
|
|
229
|
-
|
|
139
|
+
Ordered, fully-resolved migration steps from stored to current version.
|
|
230
140
|
|
|
231
|
-
|
|
141
|
+
##### loggingComponentType?
|
|
232
142
|
|
|
233
|
-
|
|
143
|
+
`string`
|
|
234
144
|
|
|
235
|
-
|
|
145
|
+
An optional logging component type for connector startup.
|
|
236
146
|
|
|
237
|
-
|
|
147
|
+
##### batchSize?
|
|
238
148
|
|
|
239
|
-
|
|
149
|
+
`number` = `100`
|
|
240
150
|
|
|
241
|
-
|
|
151
|
+
Number of entities to read and write per batch. Defaults to 100.
|
|
242
152
|
|
|
243
|
-
|
|
153
|
+
#### Returns
|
|
244
154
|
|
|
245
|
-
|
|
155
|
+
`Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
|
|
246
156
|
|
|
247
|
-
|
|
157
|
+
The finalized connector and the count of migrated entities.
|
package/docs/reference/index.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
- [IEntityStorageConnector](interfaces/IEntityStorageConnector.md)
|
|
12
12
|
- [IEntityStorageMigrationConnector](interfaces/IEntityStorageMigrationConnector.md)
|
|
13
13
|
- [IMigrationOptions](interfaces/IMigrationOptions.md)
|
|
14
|
+
- [IResolvedMigrationStep](interfaces/IResolvedMigrationStep.md)
|
|
15
|
+
- [ISchemaMigration](interfaces/ISchemaMigration.md)
|
|
14
16
|
- [IEntityStorageCountRequest](interfaces/IEntityStorageCountRequest.md)
|
|
15
17
|
- [IEntityStorageCountResponse](interfaces/IEntityStorageCountResponse.md)
|
|
16
18
|
- [IEntityStorageEmptyRequest](interfaces/IEntityStorageEmptyRequest.md)
|
|
@@ -26,3 +28,4 @@
|
|
|
26
28
|
## Variables
|
|
27
29
|
|
|
28
30
|
- [EntityStorageConnectorFactory](variables/EntityStorageConnectorFactory.md)
|
|
31
|
+
- [SchemaMigrationFactory](variables/SchemaMigrationFactory.md)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Interface: IResolvedMigrationStep\<T, U\>
|
|
2
|
+
|
|
3
|
+
A fully-resolved single migration step used by MigrationHelper.
|
|
4
|
+
The SchemaVersionService builds these by looking up versioned schema classes
|
|
5
|
+
from EntitySchemaFactory (e.g. MyEntityV0, MyEntityV1) before invoking the helper,
|
|
6
|
+
keeping factory knowledge out of the helper itself.
|
|
7
|
+
|
|
8
|
+
## Type Parameters
|
|
9
|
+
|
|
10
|
+
### T
|
|
11
|
+
|
|
12
|
+
`T` = `unknown`
|
|
13
|
+
|
|
14
|
+
The entity type. Defaults to `unknown`. Use a concrete entity type
|
|
15
|
+
when the step's source and target schemas are known at the call site.
|
|
16
|
+
|
|
17
|
+
### U
|
|
18
|
+
|
|
19
|
+
`U` = `unknown`
|
|
20
|
+
|
|
21
|
+
## Properties
|
|
22
|
+
|
|
23
|
+
### fromProperties {#fromproperties}
|
|
24
|
+
|
|
25
|
+
> **fromProperties**: `IEntitySchemaProperty`\<`T`\>[]
|
|
26
|
+
|
|
27
|
+
The property list of the entity at the start of this step (the "old" shape).
|
|
28
|
+
Sourced from the versioned schema class registered in EntitySchemaFactory,
|
|
29
|
+
e.g. EntitySchemaFactory.get("MyEntityV0").properties.
|
|
30
|
+
|
|
31
|
+
***
|
|
32
|
+
|
|
33
|
+
### toProperties {#toproperties}
|
|
34
|
+
|
|
35
|
+
> **toProperties**: `IEntitySchemaProperty`\<`U`\>[]
|
|
36
|
+
|
|
37
|
+
The property list of the entity at the end of this step (the "new" shape).
|
|
38
|
+
For the final step this is the live current schema's properties.
|
|
39
|
+
|
|
40
|
+
***
|
|
41
|
+
|
|
42
|
+
### renames? {#renames}
|
|
43
|
+
|
|
44
|
+
> `optional` **renames?**: `object`[]
|
|
45
|
+
|
|
46
|
+
Optional property renames for this step, forwarded to EntitySchemaDiffHelper.diff.
|
|
47
|
+
|
|
48
|
+
#### from
|
|
49
|
+
|
|
50
|
+
> **from**: `string`
|
|
51
|
+
|
|
52
|
+
#### to
|
|
53
|
+
|
|
54
|
+
> **to**: `string`
|
|
55
|
+
|
|
56
|
+
***
|
|
57
|
+
|
|
58
|
+
### transformEntityProperty? {#transformentityproperty}
|
|
59
|
+
|
|
60
|
+
> `optional` **transformEntityProperty?**: (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
|
|
61
|
+
|
|
62
|
+
Optional per-property transformer for object/array properties that the structural
|
|
63
|
+
diff cannot handle automatically. Sourced from an ISchemaMigration override when
|
|
64
|
+
one is registered in SchemaMigrationFactory for this step.
|
|
65
|
+
|
|
66
|
+
#### Parameters
|
|
67
|
+
|
|
68
|
+
##### schema1Property
|
|
69
|
+
|
|
70
|
+
`IEntitySchemaProperty`\<`T`\>
|
|
71
|
+
|
|
72
|
+
##### schemaProperty2
|
|
73
|
+
|
|
74
|
+
`IEntitySchemaProperty`\<`U`\>
|
|
75
|
+
|
|
76
|
+
##### value
|
|
77
|
+
|
|
78
|
+
`unknown`
|
|
79
|
+
|
|
80
|
+
#### Returns
|
|
81
|
+
|
|
82
|
+
`unknown`
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Interface: ISchemaMigration\<T, U\>
|
|
2
|
+
|
|
3
|
+
Optional per-step override for a single version-to-version migration.
|
|
4
|
+
Only register an entry in SchemaMigrationFactory when a step requires property
|
|
5
|
+
renames or a custom object/array transform. For purely structural changes
|
|
6
|
+
(add/remove/type-change fields) no entry is needed — the runner diffs the two
|
|
7
|
+
versioned schema classes (e.g. MyEntityV0 vs MyEntityV1) from EntitySchemaFactory
|
|
8
|
+
automatically.
|
|
9
|
+
|
|
10
|
+
Register under the key "<BaseSchemaName>_<fromVersion>_<toVersion>"
|
|
11
|
+
e.g. "MyEntity_0_1" for the step that migrates from version 0 to version 1.
|
|
12
|
+
The key itself encodes the version pair; no version field is needed on the object.
|
|
13
|
+
|
|
14
|
+
## Type Parameters
|
|
15
|
+
|
|
16
|
+
### T
|
|
17
|
+
|
|
18
|
+
`T` = `unknown`
|
|
19
|
+
|
|
20
|
+
### U
|
|
21
|
+
|
|
22
|
+
`U` = `unknown`
|
|
23
|
+
|
|
24
|
+
## Properties
|
|
25
|
+
|
|
26
|
+
### renames? {#renames}
|
|
27
|
+
|
|
28
|
+
> `optional` **renames?**: `object`[]
|
|
29
|
+
|
|
30
|
+
Optional property renames to apply during this step.
|
|
31
|
+
|
|
32
|
+
#### from
|
|
33
|
+
|
|
34
|
+
> **from**: `string`
|
|
35
|
+
|
|
36
|
+
#### to
|
|
37
|
+
|
|
38
|
+
> **to**: `string`
|
|
39
|
+
|
|
40
|
+
***
|
|
41
|
+
|
|
42
|
+
### transformEntityProperty? {#transformentityproperty}
|
|
43
|
+
|
|
44
|
+
> `optional` **transformEntityProperty?**: (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
|
|
45
|
+
|
|
46
|
+
Optional per-property transformer for object/array properties that cannot be
|
|
47
|
+
automatically coerced. T is the source entity type, U is the target entity type.
|
|
48
|
+
|
|
49
|
+
#### Parameters
|
|
50
|
+
|
|
51
|
+
##### schema1Property
|
|
52
|
+
|
|
53
|
+
`IEntitySchemaProperty`\<`T`\>
|
|
54
|
+
|
|
55
|
+
##### schemaProperty2
|
|
56
|
+
|
|
57
|
+
`IEntitySchemaProperty`\<`U`\>
|
|
58
|
+
|
|
59
|
+
##### value
|
|
60
|
+
|
|
61
|
+
`unknown`
|
|
62
|
+
|
|
63
|
+
#### Returns
|
|
64
|
+
|
|
65
|
+
`unknown`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Variable: SchemaMigrationFactory
|
|
2
|
+
|
|
3
|
+
> `const` **SchemaMigrationFactory**: `Factory`\<[`ISchemaMigration`](../interfaces/ISchemaMigration.md)\<`unknown`, `unknown`\>\>
|
|
4
|
+
|
|
5
|
+
Factory for optional per-step migration overrides.
|
|
6
|
+
|
|
7
|
+
Only register an entry when a version step requires property renames or a custom
|
|
8
|
+
transform hook. For purely structural changes (add/remove/type-change) the
|
|
9
|
+
SchemaVersionService diffs the two versioned schema classes automatically
|
|
10
|
+
without needing any factory entry.
|
|
11
|
+
|
|
12
|
+
Keys follow the convention "<BaseSchemaName>_<fromVersion>_<toVersion>",
|
|
13
|
+
for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
package/package.json
CHANGED