@twin.org/entity-storage-models 0.0.3-next.23 → 0.0.3-next.25
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 +1 -1
- package/dist/es/factories/schemaMigrationFactory.js.map +1 -1
- package/dist/es/helpers/migrationHelper.js +125 -86
- package/dist/es/helpers/migrationHelper.js.map +1 -1
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IEntityStorageMigrationConnector.js.map +1 -1
- package/dist/es/models/IMigrationOptions.js.map +1 -1
- package/dist/es/models/IResolvedMigrationStep.js.map +1 -1
- package/dist/es/models/ISchemaMigration.js.map +1 -1
- package/dist/es/models/entityPropertyTransformer.js +2 -0
- package/dist/es/models/entityPropertyTransformer.js.map +1 -0
- package/dist/types/factories/schemaMigrationFactory.d.ts +1 -1
- package/dist/types/helpers/migrationHelper.d.ts +34 -26
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/IEntityStorageMigrationConnector.d.ts +2 -2
- package/dist/types/models/IMigrationOptions.d.ts +6 -21
- package/dist/types/models/IResolvedMigrationStep.d.ts +7 -5
- package/dist/types/models/ISchemaMigration.d.ts +8 -5
- package/dist/types/models/entityPropertyTransformer.d.ts +6 -0
- package/docs/changelog.md +19 -0
- package/docs/reference/classes/MigrationHelper.md +91 -50
- package/docs/reference/index.md +4 -0
- package/docs/reference/interfaces/IEntityStorageMigrationConnector.md +2 -2
- package/docs/reference/interfaces/IMigrationOptions.md +9 -79
- package/docs/reference/interfaces/IResolvedMigrationStep.md +9 -13
- package/docs/reference/interfaces/ISchemaMigration.md +10 -13
- package/docs/reference/type-aliases/EntityPropertyTransformer.md +34 -0
- package/docs/reference/variables/SchemaMigrationFactory.md +1 -1
- package/locales/en.json +8 -1
- package/package.json +2 -1
|
@@ -9,7 +9,7 @@ import { Factory } from "@twin.org/core";
|
|
|
9
9
|
* SchemaVersionService diffs the two versioned schema classes automatically
|
|
10
10
|
* without needing any factory entry.
|
|
11
11
|
*
|
|
12
|
-
* Keys follow the convention "
|
|
12
|
+
* Keys follow the convention "BaseSchemaName_fromVersion_toVersion",
|
|
13
13
|
* for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
|
14
14
|
*/
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
@@ -1 +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 \"
|
|
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"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright 2026 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
import { ContextIdStore } from "@twin.org/context";
|
|
4
|
-
import { Coerce, GeneralError, Is, ObjectHelper } from "@twin.org/core";
|
|
4
|
+
import { BaseError, Coerce, ComponentFactory, GeneralError, Is, ObjectHelper } from "@twin.org/core";
|
|
5
5
|
import { EntitySchemaDiffHelper, EntitySchemaPropertyType } from "@twin.org/entity";
|
|
6
6
|
/**
|
|
7
7
|
* Helper class for performing entity schema migrations between two connectors.
|
|
@@ -13,6 +13,129 @@ export class MigrationHelper {
|
|
|
13
13
|
* Runtime name for the class.
|
|
14
14
|
*/
|
|
15
15
|
static CLASS_NAME = "MigrationHelper";
|
|
16
|
+
/**
|
|
17
|
+
* Performs a chain migration in a single connector swap, regardless of how many version
|
|
18
|
+
* steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
19
|
+
* applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
20
|
+
* A chain of one step is equivalent to a traditional single-step migration.
|
|
21
|
+
* @param sourceConnector The connector holding data at the stored schema version.
|
|
22
|
+
* @param targetSchemaName The schema name for the current version (used to create the target connector).
|
|
23
|
+
* @param steps Ordered, fully-resolved migration steps from stored to current version.
|
|
24
|
+
* @param options Optional migration options.
|
|
25
|
+
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
26
|
+
* @returns The finalized connector and the count of migrated entities.
|
|
27
|
+
*/
|
|
28
|
+
static async migrateWithChain(sourceConnector, targetSchemaName, steps, options, loggingComponentType) {
|
|
29
|
+
let targetConnector;
|
|
30
|
+
const logging = ComponentFactory.getIfExists(loggingComponentType);
|
|
31
|
+
try {
|
|
32
|
+
await logging?.log({
|
|
33
|
+
source: MigrationHelper.CLASS_NAME,
|
|
34
|
+
level: "info",
|
|
35
|
+
message: "migrateSchemaStarting",
|
|
36
|
+
data: {
|
|
37
|
+
schemaName: targetSchemaName
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
targetConnector = await sourceConnector.createTargetConnector(targetSchemaName);
|
|
41
|
+
await MigrationHelper.startupConnector(sourceConnector, loggingComponentType);
|
|
42
|
+
await MigrationHelper.startupConnector(targetConnector, loggingComponentType);
|
|
43
|
+
let partitionContextIds = await sourceConnector.getPartitionContextIds();
|
|
44
|
+
if (!Is.arrayValue(partitionContextIds)) {
|
|
45
|
+
partitionContextIds ??= [];
|
|
46
|
+
partitionContextIds.push({});
|
|
47
|
+
}
|
|
48
|
+
let migrated = 0;
|
|
49
|
+
const effectivePartitions = partitionContextIds.length > 0 ? partitionContextIds : [{}];
|
|
50
|
+
await options?.onProgress?.("partitionStart", effectivePartitions.length, 0);
|
|
51
|
+
const resolvedTarget = targetConnector;
|
|
52
|
+
for (let i = 0; i < effectivePartitions.length; i++) {
|
|
53
|
+
await options?.onProgress?.("partitionProgress", effectivePartitions.length, i);
|
|
54
|
+
await ContextIdStore.run(effectivePartitions[i], async () => {
|
|
55
|
+
migrated += await MigrationHelper.migratePartitionWithChain(sourceConnector, resolvedTarget, steps, options);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
await options?.onProgress?.("partitionEnd", effectivePartitions.length, effectivePartitions.length);
|
|
59
|
+
await logging?.log({
|
|
60
|
+
source: MigrationHelper.CLASS_NAME,
|
|
61
|
+
level: "info",
|
|
62
|
+
message: "migrateSchemaFinalizing",
|
|
63
|
+
data: {
|
|
64
|
+
schemaName: targetSchemaName
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const finalConnector = await sourceConnector.finalizeMigration(targetConnector, options, loggingComponentType);
|
|
68
|
+
await logging?.log({
|
|
69
|
+
source: MigrationHelper.CLASS_NAME,
|
|
70
|
+
level: "info",
|
|
71
|
+
message: "migrateSchemaComplete",
|
|
72
|
+
data: {
|
|
73
|
+
schemaName: targetSchemaName
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return { finalConnector, migrated };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);
|
|
80
|
+
await logging?.log({
|
|
81
|
+
source: MigrationHelper.CLASS_NAME,
|
|
82
|
+
level: "error",
|
|
83
|
+
message: "migrateSchemaFailed",
|
|
84
|
+
data: {
|
|
85
|
+
schemaName: targetSchemaName
|
|
86
|
+
},
|
|
87
|
+
error: BaseError.fromError(error)
|
|
88
|
+
});
|
|
89
|
+
throw new GeneralError(MigrationHelper.CLASS_NAME, "migrateSchemaFailed", { schemaName: targetSchemaName }, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reads all entities from one partition of the source connector, applies the migration
|
|
94
|
+
* chain to each entity, and writes the results to the target connector.
|
|
95
|
+
* @param source The connector to read from (already bootstrapped).
|
|
96
|
+
* @param target The connector to write to (already bootstrapped).
|
|
97
|
+
* @param steps Ordered, fully-resolved migration steps.
|
|
98
|
+
* @param options Optional migration options (batchSize, progress callbacks, transformEntityProperty).
|
|
99
|
+
* @returns The number of entities migrated.
|
|
100
|
+
*/
|
|
101
|
+
static async migratePartitionWithChain(source, target, steps, options) {
|
|
102
|
+
let migrated = 0;
|
|
103
|
+
let cursor;
|
|
104
|
+
const totalEntities = await source.count();
|
|
105
|
+
if (totalEntities > 0) {
|
|
106
|
+
await options?.onProgress?.("partitionItemsStart", totalEntities, 0);
|
|
107
|
+
do {
|
|
108
|
+
const page = await source.query(undefined, undefined, undefined, cursor, options?.batchSize);
|
|
109
|
+
cursor = page.cursor;
|
|
110
|
+
if (Is.arrayValue(page.entities)) {
|
|
111
|
+
const transformedBatch = page.entities.map(entity => MigrationHelper.applyEntityChain(entity, steps));
|
|
112
|
+
await target.setBatch(transformedBatch);
|
|
113
|
+
migrated += transformedBatch.length;
|
|
114
|
+
}
|
|
115
|
+
await options?.onProgress?.("partitionItemsProgress", totalEntities, migrated);
|
|
116
|
+
} while (Is.stringValue(cursor));
|
|
117
|
+
await options?.onProgress?.("partitionItemsEnd", totalEntities, totalEntities);
|
|
118
|
+
}
|
|
119
|
+
return migrated;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Transforms a single entity through an ordered chain of fully-resolved migration steps.
|
|
123
|
+
* For each step the method diffs fromProperties against toProperties, then applies
|
|
124
|
+
* applyEntityTransform. Each step's output feeds the next step's input so that
|
|
125
|
+
* per-step transformEntityProperty hooks are honoured throughout the chain.
|
|
126
|
+
* @param entity The entity to transform (at the shape described by steps[0].fromProperties).
|
|
127
|
+
* @param steps Ordered, fully-resolved migration steps from stored version to current version.
|
|
128
|
+
* Each step's fromProperties and toProperties are resolved by the caller before invocation.
|
|
129
|
+
* @returns The entity transformed to the shape described by steps[last].toProperties.
|
|
130
|
+
*/
|
|
131
|
+
static applyEntityChain(entity, steps) {
|
|
132
|
+
let current = entity;
|
|
133
|
+
for (const step of steps) {
|
|
134
|
+
const diff = EntitySchemaDiffHelper.diff(step.fromProperties, step.toProperties, step.renames);
|
|
135
|
+
current = MigrationHelper.applyEntityTransform(current, diff, step.transformEntityProperty);
|
|
136
|
+
}
|
|
137
|
+
return current;
|
|
138
|
+
}
|
|
16
139
|
/**
|
|
17
140
|
* Applies the entity transformation for a single diff, handling added, removed, and
|
|
18
141
|
* modified properties according to the provided schema diff and optional transform hook.
|
|
@@ -89,94 +212,10 @@ export class MigrationHelper {
|
|
|
89
212
|
// Removed properties are simply dropped.
|
|
90
213
|
return newEntity;
|
|
91
214
|
}
|
|
92
|
-
/**
|
|
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
215
|
/**
|
|
177
216
|
* Starts the connector by calling bootstrap and start if they are defined.
|
|
178
217
|
* @param connector The connector to start.
|
|
179
|
-
* @param loggingComponentType
|
|
218
|
+
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
180
219
|
* @internal
|
|
181
220
|
*/
|
|
182
221
|
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 } 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"]}
|
|
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 {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tGeneralError,\n\tIs,\n\tObjectHelper\n} from \"@twin.org/core\";\nimport {\n\tEntitySchemaDiffHelper,\n\tEntitySchemaPropertyType,\n\ttype IEntitySchemaDiff\n} from \"@twin.org/entity\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type {\n\tIEntityStorageMigrationConnector,\n\tIEntityStorageConnector,\n\tIMigrationOptions,\n\tEntityPropertyTransformer\n} from \"@twin.org/entity-storage-models\";\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 * 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 options Optional migration options.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\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\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<{\n\t\tfinalConnector: IEntityStorageConnector;\n\t\tmigrated: number;\n\t}> {\n\t\tlet targetConnector: IEntityStorageConnector | undefined;\n\t\tconst logging = ComponentFactory.getIfExists<ILoggingComponent>(loggingComponentType);\n\n\t\ttry {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: MigrationHelper.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"migrateSchemaStarting\",\n\t\t\t\tdata: {\n\t\t\t\t\tschemaName: targetSchemaName\n\t\t\t\t}\n\t\t\t});\n\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\tawait options?.onProgress?.(\"partitionStart\", effectivePartitions.length, 0);\n\n\t\t\tconst resolvedTarget = targetConnector;\n\t\t\tfor (let i = 0; i < effectivePartitions.length; i++) {\n\t\t\t\tawait options?.onProgress?.(\"partitionProgress\", effectivePartitions.length, i);\n\n\t\t\t\tawait ContextIdStore.run(effectivePartitions[i], 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\toptions\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait options?.onProgress?.(\n\t\t\t\t\"partitionEnd\",\n\t\t\t\teffectivePartitions.length,\n\t\t\t\teffectivePartitions.length\n\t\t\t);\n\n\t\t\tawait logging?.log({\n\t\t\t\tsource: MigrationHelper.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"migrateSchemaFinalizing\",\n\t\t\t\tdata: {\n\t\t\t\t\tschemaName: targetSchemaName\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\toptions,\n\t\t\t\tloggingComponentType\n\t\t\t);\n\n\t\t\tawait logging?.log({\n\t\t\t\tsource: MigrationHelper.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"migrateSchemaComplete\",\n\t\t\t\tdata: {\n\t\t\t\t\tschemaName: targetSchemaName\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn { finalConnector, migrated };\n\t\t} catch (error) {\n\t\t\tawait sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);\n\n\t\t\tawait logging?.log({\n\t\t\t\tsource: MigrationHelper.CLASS_NAME,\n\t\t\t\tlevel: \"error\",\n\t\t\t\tmessage: \"migrateSchemaFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tschemaName: targetSchemaName\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t\tthrow new GeneralError(\n\t\t\t\tMigrationHelper.CLASS_NAME,\n\t\t\t\t\"migrateSchemaFailed\",\n\t\t\t\t{ schemaName: targetSchemaName },\n\t\t\t\terror\n\t\t\t);\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 options Optional migration options (batchSize, progress callbacks, transformEntityProperty).\n\t * @returns The number of entities migrated.\n\t */\n\tpublic static async migratePartitionWithChain(\n\t\tsource: IEntityStorageMigrationConnector,\n\t\ttarget: IEntityStorageConnector,\n\t\tsteps: IResolvedMigrationStep[],\n\t\toptions?: IMigrationOptions\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\tawait options?.onProgress?.(\"partitionItemsStart\", totalEntities, 0);\n\n\t\t\tdo {\n\t\t\t\tconst page = await source.query(\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tcursor,\n\t\t\t\t\toptions?.batchSize\n\t\t\t\t);\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\n\t\t\t\tawait options?.onProgress?.(\"partitionItemsProgress\", totalEntities, migrated);\n\t\t\t} while (Is.stringValue(cursor));\n\n\t\t\tawait options?.onProgress?.(\"partitionItemsEnd\", totalEntities, totalEntities);\n\t\t}\n\n\t\treturn migrated;\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 * 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?: EntityPropertyTransformer<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 = 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 * Starts the connector by calling bootstrap and start if they are defined.\n\t * @param connector The connector to start.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @internal\n\t */\n\tprivate static async startupConnector<T>(\n\t\tconnector: IEntityStorageConnector<T>,\n\t\tloggingComponentType?: string\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
|
@@ -15,6 +15,7 @@ export * from "./models/api/IEntityStorageRemoveBatchRequest.js";
|
|
|
15
15
|
export * from "./models/api/IEntityStorageRemoveRequest.js";
|
|
16
16
|
export * from "./models/api/IEntityStorageSetBatchRequest.js";
|
|
17
17
|
export * from "./models/api/IEntityStorageSetRequest.js";
|
|
18
|
+
export * from "./models/entityPropertyTransformer.js";
|
|
18
19
|
export * from "./models/IEntityStorageComponent.js";
|
|
19
20
|
export * from "./models/IEntityStorageConnector.js";
|
|
20
21
|
export * from "./models/IEntityStorageMigrationConnector.js";
|
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,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"]}
|
|
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,uCAAuC,CAAC;AACtD,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/entityPropertyTransformer.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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageMigrationConnector.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageMigrationConnector.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IContextIds } from \"@twin.org/context\";\nimport type { IEntityStorageConnector } from \"./IEntityStorageConnector.js\";\nimport type { IMigrationOptions } from \"./IMigrationOptions.js\";\n\n/**\n * Interface describing an entity storage migration connector.\n */\nexport interface IEntityStorageMigrationConnector<T = unknown> extends IEntityStorageConnector<T> {\n\t/**\n\t * Get a unique list of all the context ids from the storage.\n\t * @returns The list of unique context ids.\n\t */\n\tgetPartitionContextIds(): Promise<IContextIds[]>;\n\n\t/**\n\t * Create the target connector for performing the migration it will use a temporary storage location.\n\t * @param newEntitySchema The name of the new entity schema to create the connector for.\n\t * @returns Connector for performing the migration.\n\t */\n\tcreateTargetConnector<U>(newEntitySchema: string): Promise<IEntityStorageConnector<U>>;\n\n\t/**\n\t * Finalize the migration by tearing down the old connector and replacing it with the target connector.\n\t * @param targetConnector The target connector to finalize the migration with.\n\t * @param options The options to control how the migration is finalized.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is finalized and returns the final connector.\n\t */\n\tfinalizeMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U>,\n\t\toptions?: IMigrationOptions
|
|
1
|
+
{"version":3,"file":"IEntityStorageMigrationConnector.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageMigrationConnector.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IContextIds } from \"@twin.org/context\";\nimport type { IEntityStorageConnector } from \"./IEntityStorageConnector.js\";\nimport type { IMigrationOptions } from \"./IMigrationOptions.js\";\n\n/**\n * Interface describing an entity storage migration connector.\n */\nexport interface IEntityStorageMigrationConnector<T = unknown> extends IEntityStorageConnector<T> {\n\t/**\n\t * Get a unique list of all the context ids from the storage.\n\t * @returns The list of unique context ids.\n\t */\n\tgetPartitionContextIds(): Promise<IContextIds[]>;\n\n\t/**\n\t * Create the target connector for performing the migration it will use a temporary storage location.\n\t * @param newEntitySchema The name of the new entity schema to create the connector for.\n\t * @returns Connector for performing the migration.\n\t */\n\tcreateTargetConnector<U>(newEntitySchema: string): Promise<IEntityStorageConnector<U>>;\n\n\t/**\n\t * Finalize the migration by tearing down the old connector and replacing it with the target connector.\n\t * @param targetConnector The target connector to finalize the migration with.\n\t * @param options The options to control how the migration is finalized.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is finalized and returns the final connector.\n\t */\n\tfinalizeMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U>,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<IEntityStorageConnector<U>>;\n\n\t/**\n\t * Cleanup the migration if a migration fails or needs to be aborted.\n\t * @param targetConnector The target connector to cleanup the migration with.\n\t * @param options The options to control how the migration is cleaned up.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is cleaned up.\n\t */\n\tcleanupMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U> | undefined,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<void>;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IMigrationOptions.js","sourceRoot":"","sources":["../../../src/models/IMigrationOptions.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\
|
|
1
|
+
{"version":3,"file":"IMigrationOptions.js","sourceRoot":"","sources":["../../../src/models/IMigrationOptions.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Options controlling how a schema migration is executed.\n */\nexport interface IMigrationOptions {\n\t/**\n\t * Number of entities to read and write per batch.\n\t * @default 100\n\t */\n\tbatchSize?: number;\n\n\t/**\n\t * Called for progress tracking.\n\t * @param progressItem The item progress being updated.\n\t * @param itemTotal The total number of rows to migrate.\n\t * @param itemIndex The number of rows migrated so far.\n\t */\n\tonProgress?: (\n\t\tprogressItem:\n\t\t\t| \"partitionStart\"\n\t\t\t| \"partitionProgress\"\n\t\t\t| \"partitionEnd\"\n\t\t\t| \"partitionItemsStart\"\n\t\t\t| \"partitionItemsProgress\"\n\t\t\t| \"partitionItemsEnd\",\n\t\titemTotal: number,\n\t\titemIndex: number\n\t) => Promise<void>;\n}\n"]}
|
|
@@ -1 +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 {
|
|
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 { EntityPropertyTransformer } from \"@twin.org/entity-storage-models\";\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 transformation for properties, usually only called for object and array types.\n\t * @param schema1Property The property schema in the old schema.\n\t * @param schemaProperty2 The property schema in the new schema.\n\t * @param value The value of the property in the old schema.\n\t * @returns The transformed value to match the new schema.\n\t */\n\ttransformEntityProperty?: EntityPropertyTransformer<T, U>;\n}\n"]}
|
|
@@ -1 +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 {
|
|
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 { EntityPropertyTransformer } from \"./entityPropertyTransformer.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 transformation for properties, usually only called for object and array types.\n\t * @param schema1Property The property schema in the old schema.\n\t * @param schemaProperty2 The property schema in the new schema.\n\t * @param value The value of the property in the old schema.\n\t * @returns The transformed value to match the new schema.\n\t */\n\ttransformEntityProperty?: EntityPropertyTransformer<T, U>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityPropertyTransformer.js","sourceRoot":"","sources":["../../../src/models/entityPropertyTransformer.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IEntitySchemaProperty } from \"@twin.org/entity\";\n\n/**\n * Type for the optional transformEntityProperty function.\n * Used in migration steps for custom property transformations.\n */\nexport type EntityPropertyTransformer<T = unknown, U = unknown> = (\n\tschema1Property: IEntitySchemaProperty<T>,\n\tschemaProperty2: IEntitySchemaProperty<U>,\n\tvalue: unknown\n) => unknown;\n"]}
|
|
@@ -8,7 +8,7 @@ import type { ISchemaMigration } from "../models/ISchemaMigration.js";
|
|
|
8
8
|
* SchemaVersionService diffs the two versioned schema classes automatically
|
|
9
9
|
* without needing any factory entry.
|
|
10
10
|
*
|
|
11
|
-
* Keys follow the convention "
|
|
11
|
+
* Keys follow the convention "BaseSchemaName_fromVersion_toVersion",
|
|
12
12
|
* for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
|
13
13
|
*/
|
|
14
14
|
export declare const SchemaMigrationFactory: Factory<ISchemaMigration<unknown, unknown>>;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type IEntitySchemaDiff } from "@twin.org/entity";
|
|
2
|
-
import type { IEntityStorageConnector } from "
|
|
3
|
-
import type { IEntityStorageMigrationConnector } from "../models/IEntityStorageMigrationConnector.js";
|
|
4
|
-
import type { IMigrationOptions } from "../models/IMigrationOptions.js";
|
|
2
|
+
import type { IEntityStorageMigrationConnector, IEntityStorageConnector, IMigrationOptions, EntityPropertyTransformer } from "@twin.org/entity-storage-models";
|
|
5
3
|
import type { IResolvedMigrationStep } from "../models/IResolvedMigrationStep.js";
|
|
6
4
|
/**
|
|
7
5
|
* Helper class for performing entity schema migrations between two connectors.
|
|
@@ -14,16 +12,31 @@ export declare class MigrationHelper {
|
|
|
14
12
|
*/
|
|
15
13
|
static readonly CLASS_NAME: string;
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param
|
|
22
|
-
* @
|
|
23
|
-
* @
|
|
24
|
-
* @
|
|
15
|
+
* Performs a chain migration in a single connector swap, regardless of how many version
|
|
16
|
+
* steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
17
|
+
* applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
18
|
+
* A chain of one step is equivalent to a traditional single-step migration.
|
|
19
|
+
* @param sourceConnector The connector holding data at the stored schema version.
|
|
20
|
+
* @param targetSchemaName The schema name for the current version (used to create the target connector).
|
|
21
|
+
* @param steps Ordered, fully-resolved migration steps from stored to current version.
|
|
22
|
+
* @param options Optional migration options.
|
|
23
|
+
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
24
|
+
* @returns The finalized connector and the count of migrated entities.
|
|
25
|
+
*/
|
|
26
|
+
static migrateWithChain(sourceConnector: IEntityStorageMigrationConnector, targetSchemaName: string, steps: IResolvedMigrationStep[], options?: IMigrationOptions, loggingComponentType?: string): Promise<{
|
|
27
|
+
finalConnector: IEntityStorageConnector;
|
|
28
|
+
migrated: number;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Reads all entities from one partition of the source connector, applies the migration
|
|
32
|
+
* chain to each entity, and writes the results to the target connector.
|
|
33
|
+
* @param source The connector to read from (already bootstrapped).
|
|
34
|
+
* @param target The connector to write to (already bootstrapped).
|
|
35
|
+
* @param steps Ordered, fully-resolved migration steps.
|
|
36
|
+
* @param options Optional migration options (batchSize, progress callbacks, transformEntityProperty).
|
|
37
|
+
* @returns The number of entities migrated.
|
|
25
38
|
*/
|
|
26
|
-
static
|
|
39
|
+
static migratePartitionWithChain(source: IEntityStorageMigrationConnector, target: IEntityStorageConnector, steps: IResolvedMigrationStep[], options?: IMigrationOptions): Promise<number>;
|
|
27
40
|
/**
|
|
28
41
|
* Transforms a single entity through an ordered chain of fully-resolved migration steps.
|
|
29
42
|
* For each step the method diffs fromProperties against toProperties, then applies
|
|
@@ -36,19 +49,14 @@ export declare class MigrationHelper {
|
|
|
36
49
|
*/
|
|
37
50
|
static applyEntityChain(entity: unknown, steps: IResolvedMigrationStep[]): unknown;
|
|
38
51
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
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.
|
|
52
|
+
* Applies the entity transformation for a single diff, handling added, removed, and
|
|
53
|
+
* modified properties according to the provided schema diff and optional transform hook.
|
|
54
|
+
* @param entity The entity to transform.
|
|
55
|
+
* @param schemaDiff The schema diff between the old and new schemas.
|
|
56
|
+
* @param transformEntityProperty Optional per-property transform hook for object/array properties.
|
|
57
|
+
* @returns The transformed entity ready to be written to the new schema.
|
|
58
|
+
* @throws GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
|
|
59
|
+
* @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
49
60
|
*/
|
|
50
|
-
static
|
|
51
|
-
finalConnector: IEntityStorageConnector;
|
|
52
|
-
migrated: number;
|
|
53
|
-
}>;
|
|
61
|
+
static applyEntityTransform<T = unknown, U = unknown>(entity: Partial<T>, schemaDiff: IEntitySchemaDiff<T, U>, transformEntityProperty?: EntityPropertyTransformer<T, U>): U;
|
|
54
62
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./models/api/IEntityStorageRemoveBatchRequest.js";
|
|
|
13
13
|
export * from "./models/api/IEntityStorageRemoveRequest.js";
|
|
14
14
|
export * from "./models/api/IEntityStorageSetBatchRequest.js";
|
|
15
15
|
export * from "./models/api/IEntityStorageSetRequest.js";
|
|
16
|
+
export * from "./models/entityPropertyTransformer.js";
|
|
16
17
|
export * from "./models/IEntityStorageComponent.js";
|
|
17
18
|
export * from "./models/IEntityStorageConnector.js";
|
|
18
19
|
export * from "./models/IEntityStorageMigrationConnector.js";
|
|
@@ -23,7 +23,7 @@ export interface IEntityStorageMigrationConnector<T = unknown> extends IEntitySt
|
|
|
23
23
|
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
24
24
|
* @returns A promise that resolves when the migration is finalized and returns the final connector.
|
|
25
25
|
*/
|
|
26
|
-
finalizeMigration<U>(targetConnector: IEntityStorageConnector<U>, options?: IMigrationOptions
|
|
26
|
+
finalizeMigration<U>(targetConnector: IEntityStorageConnector<U>, options?: IMigrationOptions, loggingComponentType?: string): Promise<IEntityStorageConnector<U>>;
|
|
27
27
|
/**
|
|
28
28
|
* Cleanup the migration if a migration fails or needs to be aborted.
|
|
29
29
|
* @param targetConnector The target connector to cleanup the migration with.
|
|
@@ -31,5 +31,5 @@ export interface IEntityStorageMigrationConnector<T = unknown> extends IEntitySt
|
|
|
31
31
|
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
32
32
|
* @returns A promise that resolves when the migration is cleaned up.
|
|
33
33
|
*/
|
|
34
|
-
cleanupMigration<U>(targetConnector: IEntityStorageConnector<U> | undefined, options?: IMigrationOptions
|
|
34
|
+
cleanupMigration<U>(targetConnector: IEntityStorageConnector<U> | undefined, options?: IMigrationOptions, loggingComponentType?: string): Promise<void>;
|
|
35
35
|
}
|
|
@@ -1,32 +1,17 @@
|
|
|
1
|
-
import type { IEntitySchemaProperty } from "@twin.org/entity";
|
|
2
1
|
/**
|
|
3
2
|
* Options controlling how a schema migration is executed.
|
|
4
3
|
*/
|
|
5
|
-
export interface IMigrationOptions
|
|
4
|
+
export interface IMigrationOptions {
|
|
6
5
|
/**
|
|
7
6
|
* Number of entities to read and write per batch.
|
|
8
7
|
* @default 100
|
|
9
8
|
*/
|
|
10
9
|
batchSize?: number;
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @returns The transformed value to match the new schema.
|
|
11
|
+
* Called for progress tracking.
|
|
12
|
+
* @param progressItem The item progress being updated.
|
|
13
|
+
* @param itemTotal The total number of rows to migrate.
|
|
14
|
+
* @param itemIndex The number of rows migrated so far.
|
|
17
15
|
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Called for each partition for progress tracking.
|
|
21
|
-
* @param rowTotal The total number of rows to migrate.
|
|
22
|
-
* @param rowIndex The number of rows migrated so far.
|
|
23
|
-
*/
|
|
24
|
-
onPartitionProgress?: (rowTotal: number, rowIndex: number) => Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
* Called for overall progress tracking.
|
|
27
|
-
* @param stepKey The key representing the current step in the migration.
|
|
28
|
-
* @param itemTotal The total number of items in this progress.
|
|
29
|
-
* @param itemIndex The number of items processed so far.
|
|
30
|
-
*/
|
|
31
|
-
onStepProgress?: (stepKey: string, itemTotal: number, itemIndex: number) => Promise<void>;
|
|
16
|
+
onProgress?: (progressItem: "partitionStart" | "partitionProgress" | "partitionEnd" | "partitionItemsStart" | "partitionItemsProgress" | "partitionItemsEnd", itemTotal: number, itemIndex: number) => Promise<void>;
|
|
32
17
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IEntitySchemaProperty } from "@twin.org/entity";
|
|
2
|
-
import type {
|
|
2
|
+
import type { EntityPropertyTransformer } from "@twin.org/entity-storage-models";
|
|
3
3
|
/**
|
|
4
4
|
* A fully-resolved single migration step used by MigrationHelper.
|
|
5
5
|
* The SchemaVersionService builds these by looking up versioned schema classes
|
|
@@ -28,9 +28,11 @@ export interface IResolvedMigrationStep<T = unknown, U = unknown> {
|
|
|
28
28
|
to: string;
|
|
29
29
|
}[];
|
|
30
30
|
/**
|
|
31
|
-
* Optional
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Optional transformation for properties, usually only called for object and array types.
|
|
32
|
+
* @param schema1Property The property schema in the old schema.
|
|
33
|
+
* @param schemaProperty2 The property schema in the new schema.
|
|
34
|
+
* @param value The value of the property in the old schema.
|
|
35
|
+
* @returns The transformed value to match the new schema.
|
|
34
36
|
*/
|
|
35
|
-
transformEntityProperty?:
|
|
37
|
+
transformEntityProperty?: EntityPropertyTransformer<T, U>;
|
|
36
38
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EntityPropertyTransformer } from "./entityPropertyTransformer.js";
|
|
2
2
|
/**
|
|
3
3
|
* Optional per-step override for a single version-to-version migration.
|
|
4
4
|
* Only register an entry in SchemaMigrationFactory when a step requires property
|
|
@@ -7,7 +7,7 @@ import type { IMigrationOptions } from "./IMigrationOptions.js";
|
|
|
7
7
|
* versioned schema classes (e.g. MyEntityV0 vs MyEntityV1) from EntitySchemaFactory
|
|
8
8
|
* automatically.
|
|
9
9
|
*
|
|
10
|
-
* Register under the key "
|
|
10
|
+
* Register under the key "BaseSchemaName_fromVersion_toVersion"
|
|
11
11
|
* e.g. "MyEntity_0_1" for the step that migrates from version 0 to version 1.
|
|
12
12
|
* The key itself encodes the version pair; no version field is needed on the object.
|
|
13
13
|
*/
|
|
@@ -20,8 +20,11 @@ export interface ISchemaMigration<T = unknown, U = unknown> {
|
|
|
20
20
|
to: string;
|
|
21
21
|
}[];
|
|
22
22
|
/**
|
|
23
|
-
* Optional
|
|
24
|
-
*
|
|
23
|
+
* Optional transformation for properties, usually only called for object and array types.
|
|
24
|
+
* @param schema1Property The property schema in the old schema.
|
|
25
|
+
* @param schemaProperty2 The property schema in the new schema.
|
|
26
|
+
* @param value The value of the property in the old schema.
|
|
27
|
+
* @returns The transformed value to match the new schema.
|
|
25
28
|
*/
|
|
26
|
-
transformEntityProperty?:
|
|
29
|
+
transformEntityProperty?: EntityPropertyTransformer<T, U>;
|
|
27
30
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IEntitySchemaProperty } from "@twin.org/entity";
|
|
2
|
+
/**
|
|
3
|
+
* Type for the optional transformEntityProperty function.
|
|
4
|
+
* Used in migration steps for custom property transformations.
|
|
5
|
+
*/
|
|
6
|
+
export type EntityPropertyTransformer<T = unknown, U = unknown> = (schema1Property: IEntitySchemaProperty<T>, schemaProperty2: IEntitySchemaProperty<U>, value: unknown) => unknown;
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.25](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.24...entity-storage-models-v0.0.3-next.25) (2026-06-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* migration progress ([#121](https://github.com/iotaledger/twin-entity-storage/issues/121)) ([d032162](https://github.com/iotaledger/twin-entity-storage/commit/d032162768b6b7d4ccca7e39b80f8bc3ba46440e))
|
|
9
|
+
|
|
10
|
+
## [0.0.3-next.24](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.23...entity-storage-models-v0.0.3-next.24) (2026-06-08)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add SchemaVersionService for automatic schema migrations ([#118](https://github.com/iotaledger/twin-entity-storage/issues/118)) ([b2ad843](https://github.com/iotaledger/twin-entity-storage/commit/b2ad8435185c53304aca99eb4d98582009b3902d))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* docs ([1a30170](https://github.com/iotaledger/twin-entity-storage/commit/1a301707ee0cb48314223347a6e9f3b3a3a7362d))
|
|
21
|
+
|
|
3
22
|
## [0.0.3-next.23](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-models-v0.0.3-next.22...entity-storage-models-v0.0.3-next.23) (2026-06-08)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -24,56 +24,93 @@ Runtime name for the class.
|
|
|
24
24
|
|
|
25
25
|
## Methods
|
|
26
26
|
|
|
27
|
-
###
|
|
27
|
+
### migrateWithChain() {#migratewithchain}
|
|
28
28
|
|
|
29
|
-
> `static` **
|
|
29
|
+
> `static` **migrateWithChain**(`sourceConnector`, `targetSchemaName`, `steps`, `options?`, `loggingComponentType?`): `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Performs a chain migration in a single connector swap, regardless of how many version
|
|
32
|
+
steps the chain spans. Creates one target connector, reads all source entities, applies
|
|
33
|
+
applyEntityChain to each, writes them to the target, then finalizes the migration.
|
|
34
|
+
A chain of one step is equivalent to a traditional single-step migration.
|
|
33
35
|
|
|
34
|
-
####
|
|
36
|
+
#### Parameters
|
|
35
37
|
|
|
36
|
-
#####
|
|
38
|
+
##### sourceConnector
|
|
37
39
|
|
|
38
|
-
`
|
|
40
|
+
[`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
The connector holding data at the stored schema version.
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
##### targetSchemaName
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
`string`
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
The schema name for the current version (used to create the target connector).
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
##### steps
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
[`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
Ordered, fully-resolved migration steps from stored to current version.
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
##### options?
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
[`IMigrationOptions`](../interfaces/IMigrationOptions.md)
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
Optional migration options.
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
##### loggingComponentType?
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
`string`
|
|
65
|
+
|
|
66
|
+
The optional component type to use for logging the migration progress.
|
|
63
67
|
|
|
64
68
|
#### Returns
|
|
65
69
|
|
|
66
|
-
`
|
|
70
|
+
`Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
|
|
67
71
|
|
|
68
|
-
The
|
|
72
|
+
The finalized connector and the count of migrated entities.
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
***
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
### migratePartitionWithChain() {#migratepartitionwithchain}
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
> `static` **migratePartitionWithChain**(`source`, `target`, `steps`, `options?`): `Promise`\<`number`\>
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
Reads all entities from one partition of the source connector, applies the migration
|
|
81
|
+
chain to each entity, and writes the results to the target connector.
|
|
82
|
+
|
|
83
|
+
#### Parameters
|
|
84
|
+
|
|
85
|
+
##### source
|
|
86
|
+
|
|
87
|
+
[`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)
|
|
88
|
+
|
|
89
|
+
The connector to read from (already bootstrapped).
|
|
90
|
+
|
|
91
|
+
##### target
|
|
92
|
+
|
|
93
|
+
[`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md)
|
|
94
|
+
|
|
95
|
+
The connector to write to (already bootstrapped).
|
|
96
|
+
|
|
97
|
+
##### steps
|
|
98
|
+
|
|
99
|
+
[`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
|
|
100
|
+
|
|
101
|
+
Ordered, fully-resolved migration steps.
|
|
102
|
+
|
|
103
|
+
##### options?
|
|
104
|
+
|
|
105
|
+
[`IMigrationOptions`](../interfaces/IMigrationOptions.md)
|
|
106
|
+
|
|
107
|
+
Optional migration options (batchSize, progress callbacks, transformEntityProperty).
|
|
108
|
+
|
|
109
|
+
#### Returns
|
|
110
|
+
|
|
111
|
+
`Promise`\<`number`\>
|
|
112
|
+
|
|
113
|
+
The number of entities migrated.
|
|
77
114
|
|
|
78
115
|
***
|
|
79
116
|
|
|
@@ -109,49 +146,53 @@ The entity transformed to the shape described by steps[last].toProperties.
|
|
|
109
146
|
|
|
110
147
|
***
|
|
111
148
|
|
|
112
|
-
###
|
|
113
|
-
|
|
114
|
-
> `static` **migrateWithChain**(`sourceConnector`, `targetSchemaName`, `steps`, `loggingComponentType?`, `batchSize?`): `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
|
|
149
|
+
### applyEntityTransform() {#applyentitytransform}
|
|
115
150
|
|
|
116
|
-
|
|
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.
|
|
151
|
+
> `static` **applyEntityTransform**\<`T`, `U`\>(`entity`, `schemaDiff`, `transformEntityProperty?`): `U`
|
|
120
152
|
|
|
121
|
-
|
|
153
|
+
Applies the entity transformation for a single diff, handling added, removed, and
|
|
154
|
+
modified properties according to the provided schema diff and optional transform hook.
|
|
122
155
|
|
|
123
|
-
|
|
156
|
+
#### Type Parameters
|
|
124
157
|
|
|
125
|
-
|
|
158
|
+
##### T
|
|
126
159
|
|
|
127
|
-
|
|
160
|
+
`T` = `unknown`
|
|
128
161
|
|
|
129
|
-
#####
|
|
162
|
+
##### U
|
|
130
163
|
|
|
131
|
-
`
|
|
164
|
+
`U` = `unknown`
|
|
132
165
|
|
|
133
|
-
|
|
166
|
+
#### Parameters
|
|
134
167
|
|
|
135
|
-
#####
|
|
168
|
+
##### entity
|
|
136
169
|
|
|
137
|
-
|
|
170
|
+
`Partial`\<`T`\>
|
|
138
171
|
|
|
139
|
-
|
|
172
|
+
The entity to transform.
|
|
140
173
|
|
|
141
|
-
#####
|
|
174
|
+
##### schemaDiff
|
|
142
175
|
|
|
143
|
-
`
|
|
176
|
+
`IEntitySchemaDiff`\<`T`, `U`\>
|
|
144
177
|
|
|
145
|
-
|
|
178
|
+
The schema diff between the old and new schemas.
|
|
146
179
|
|
|
147
|
-
#####
|
|
180
|
+
##### transformEntityProperty?
|
|
148
181
|
|
|
149
|
-
`
|
|
182
|
+
[`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
|
|
150
183
|
|
|
151
|
-
|
|
184
|
+
Optional per-property transform hook for object/array properties.
|
|
152
185
|
|
|
153
186
|
#### Returns
|
|
154
187
|
|
|
155
|
-
`
|
|
188
|
+
`U`
|
|
156
189
|
|
|
157
|
-
The
|
|
190
|
+
The transformed entity ready to be written to the new schema.
|
|
191
|
+
|
|
192
|
+
#### Throws
|
|
193
|
+
|
|
194
|
+
GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
|
|
195
|
+
|
|
196
|
+
#### Throws
|
|
197
|
+
|
|
198
|
+
GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
package/docs/reference/index.md
CHANGED
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
- [IEntityStorageSetBatchRequest](interfaces/IEntityStorageSetBatchRequest.md)
|
|
26
26
|
- [IEntityStorageSetRequest](interfaces/IEntityStorageSetRequest.md)
|
|
27
27
|
|
|
28
|
+
## Type Aliases
|
|
29
|
+
|
|
30
|
+
- [EntityPropertyTransformer](type-aliases/EntityPropertyTransformer.md)
|
|
31
|
+
|
|
28
32
|
## Variables
|
|
29
33
|
|
|
30
34
|
- [EntityStorageConnectorFactory](variables/EntityStorageConnectorFactory.md)
|
|
@@ -345,7 +345,7 @@ The target connector to finalize the migration with.
|
|
|
345
345
|
|
|
346
346
|
##### options?
|
|
347
347
|
|
|
348
|
-
[`IMigrationOptions`](IMigrationOptions.md)
|
|
348
|
+
[`IMigrationOptions`](IMigrationOptions.md)
|
|
349
349
|
|
|
350
350
|
The options to control how the migration is finalized.
|
|
351
351
|
|
|
@@ -385,7 +385,7 @@ The target connector to cleanup the migration with.
|
|
|
385
385
|
|
|
386
386
|
##### options?
|
|
387
387
|
|
|
388
|
-
[`IMigrationOptions`](IMigrationOptions.md)
|
|
388
|
+
[`IMigrationOptions`](IMigrationOptions.md)
|
|
389
389
|
|
|
390
390
|
The options to control how the migration is cleaned up.
|
|
391
391
|
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
# Interface: IMigrationOptions
|
|
1
|
+
# Interface: IMigrationOptions
|
|
2
2
|
|
|
3
3
|
Options controlling how a schema migration is executed.
|
|
4
4
|
|
|
5
|
-
## Type Parameters
|
|
6
|
-
|
|
7
|
-
### T
|
|
8
|
-
|
|
9
|
-
`T`
|
|
10
|
-
|
|
11
|
-
### U
|
|
12
|
-
|
|
13
|
-
`U`
|
|
14
|
-
|
|
15
5
|
## Properties
|
|
16
6
|
|
|
17
7
|
### batchSize? {#batchsize}
|
|
@@ -28,91 +18,31 @@ Number of entities to read and write per batch.
|
|
|
28
18
|
|
|
29
19
|
***
|
|
30
20
|
|
|
31
|
-
###
|
|
32
|
-
|
|
33
|
-
> `optional` **transformEntityProperty?**: (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
|
|
34
|
-
|
|
35
|
-
Optional transformation for properties, usually only called for object and array types.
|
|
36
|
-
|
|
37
|
-
#### Parameters
|
|
38
|
-
|
|
39
|
-
##### schema1Property
|
|
40
|
-
|
|
41
|
-
`IEntitySchemaProperty`\<`T`\>
|
|
42
|
-
|
|
43
|
-
The property schema in the old schema.
|
|
44
|
-
|
|
45
|
-
##### schemaProperty2
|
|
46
|
-
|
|
47
|
-
`IEntitySchemaProperty`\<`U`\>
|
|
48
|
-
|
|
49
|
-
The property schema in the new schema.
|
|
50
|
-
|
|
51
|
-
##### value
|
|
52
|
-
|
|
53
|
-
`unknown`
|
|
54
|
-
|
|
55
|
-
The value of the property in the old schema.
|
|
56
|
-
|
|
57
|
-
#### Returns
|
|
58
|
-
|
|
59
|
-
`unknown`
|
|
60
|
-
|
|
61
|
-
The transformed value to match the new schema.
|
|
62
|
-
|
|
63
|
-
***
|
|
64
|
-
|
|
65
|
-
### onPartitionProgress? {#onpartitionprogress}
|
|
66
|
-
|
|
67
|
-
> `optional` **onPartitionProgress?**: (`rowTotal`, `rowIndex`) => `Promise`\<`void`\>
|
|
68
|
-
|
|
69
|
-
Called for each partition for progress tracking.
|
|
70
|
-
|
|
71
|
-
#### Parameters
|
|
72
|
-
|
|
73
|
-
##### rowTotal
|
|
74
|
-
|
|
75
|
-
`number`
|
|
76
|
-
|
|
77
|
-
The total number of rows to migrate.
|
|
78
|
-
|
|
79
|
-
##### rowIndex
|
|
80
|
-
|
|
81
|
-
`number`
|
|
82
|
-
|
|
83
|
-
The number of rows migrated so far.
|
|
84
|
-
|
|
85
|
-
#### Returns
|
|
86
|
-
|
|
87
|
-
`Promise`\<`void`\>
|
|
88
|
-
|
|
89
|
-
***
|
|
90
|
-
|
|
91
|
-
### onStepProgress? {#onstepprogress}
|
|
21
|
+
### onProgress? {#onprogress}
|
|
92
22
|
|
|
93
|
-
> `optional` **
|
|
23
|
+
> `optional` **onProgress?**: (`progressItem`, `itemTotal`, `itemIndex`) => `Promise`\<`void`\>
|
|
94
24
|
|
|
95
|
-
Called for
|
|
25
|
+
Called for progress tracking.
|
|
96
26
|
|
|
97
27
|
#### Parameters
|
|
98
28
|
|
|
99
|
-
#####
|
|
29
|
+
##### progressItem
|
|
100
30
|
|
|
101
|
-
`
|
|
31
|
+
`"partitionStart"` \| `"partitionProgress"` \| `"partitionEnd"` \| `"partitionItemsStart"` \| `"partitionItemsProgress"` \| `"partitionItemsEnd"`
|
|
102
32
|
|
|
103
|
-
The
|
|
33
|
+
The item progress being updated.
|
|
104
34
|
|
|
105
35
|
##### itemTotal
|
|
106
36
|
|
|
107
37
|
`number`
|
|
108
38
|
|
|
109
|
-
The total number of
|
|
39
|
+
The total number of rows to migrate.
|
|
110
40
|
|
|
111
41
|
##### itemIndex
|
|
112
42
|
|
|
113
43
|
`number`
|
|
114
44
|
|
|
115
|
-
The number of
|
|
45
|
+
The number of rows migrated so far.
|
|
116
46
|
|
|
117
47
|
#### Returns
|
|
118
48
|
|
|
@@ -57,26 +57,22 @@ Optional property renames for this step, forwarded to EntitySchemaDiffHelper.dif
|
|
|
57
57
|
|
|
58
58
|
### transformEntityProperty? {#transformentityproperty}
|
|
59
59
|
|
|
60
|
-
> `optional` **transformEntityProperty?**:
|
|
60
|
+
> `optional` **transformEntityProperty?**: [`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
|
|
61
61
|
|
|
62
|
-
Optional
|
|
63
|
-
diff cannot handle automatically. Sourced from an ISchemaMigration override when
|
|
64
|
-
one is registered in SchemaMigrationFactory for this step.
|
|
62
|
+
Optional transformation for properties, usually only called for object and array types.
|
|
65
63
|
|
|
66
|
-
####
|
|
64
|
+
#### Param
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
The property schema in the old schema.
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
#### Param
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
The property schema in the new schema.
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
#### Param
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`unknown`
|
|
74
|
+
The value of the property in the old schema.
|
|
79
75
|
|
|
80
76
|
#### Returns
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
The transformed value to match the new schema.
|
|
@@ -7,7 +7,7 @@ renames or a custom object/array transform. For purely structural changes
|
|
|
7
7
|
versioned schema classes (e.g. MyEntityV0 vs MyEntityV1) from EntitySchemaFactory
|
|
8
8
|
automatically.
|
|
9
9
|
|
|
10
|
-
Register under the key "
|
|
10
|
+
Register under the key "BaseSchemaName_fromVersion_toVersion"
|
|
11
11
|
e.g. "MyEntity_0_1" for the step that migrates from version 0 to version 1.
|
|
12
12
|
The key itself encodes the version pair; no version field is needed on the object.
|
|
13
13
|
|
|
@@ -41,25 +41,22 @@ Optional property renames to apply during this step.
|
|
|
41
41
|
|
|
42
42
|
### transformEntityProperty? {#transformentityproperty}
|
|
43
43
|
|
|
44
|
-
> `optional` **transformEntityProperty?**:
|
|
44
|
+
> `optional` **transformEntityProperty?**: [`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
|
|
45
45
|
|
|
46
|
-
Optional
|
|
47
|
-
automatically coerced. T is the source entity type, U is the target entity type.
|
|
46
|
+
Optional transformation for properties, usually only called for object and array types.
|
|
48
47
|
|
|
49
|
-
####
|
|
48
|
+
#### Param
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
The property schema in the old schema.
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
#### Param
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
The property schema in the new schema.
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
#### Param
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`unknown`
|
|
58
|
+
The value of the property in the old schema.
|
|
62
59
|
|
|
63
60
|
#### Returns
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
The transformed value to match the new schema.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Type Alias: EntityPropertyTransformer\<T, U\>
|
|
2
|
+
|
|
3
|
+
> **EntityPropertyTransformer**\<`T`, `U`\> = (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
|
|
4
|
+
|
|
5
|
+
Type for the optional transformEntityProperty function.
|
|
6
|
+
Used in migration steps for custom property transformations.
|
|
7
|
+
|
|
8
|
+
## Type Parameters
|
|
9
|
+
|
|
10
|
+
### T
|
|
11
|
+
|
|
12
|
+
`T` = `unknown`
|
|
13
|
+
|
|
14
|
+
### U
|
|
15
|
+
|
|
16
|
+
`U` = `unknown`
|
|
17
|
+
|
|
18
|
+
## Parameters
|
|
19
|
+
|
|
20
|
+
### schema1Property
|
|
21
|
+
|
|
22
|
+
`IEntitySchemaProperty`\<`T`\>
|
|
23
|
+
|
|
24
|
+
### schemaProperty2
|
|
25
|
+
|
|
26
|
+
`IEntitySchemaProperty`\<`U`\>
|
|
27
|
+
|
|
28
|
+
### value
|
|
29
|
+
|
|
30
|
+
`unknown`
|
|
31
|
+
|
|
32
|
+
## Returns
|
|
33
|
+
|
|
34
|
+
`unknown`
|
|
@@ -9,5 +9,5 @@ transform hook. For purely structural changes (add/remove/type-change) the
|
|
|
9
9
|
SchemaVersionService diffs the two versioned schema classes automatically
|
|
10
10
|
without needing any factory entry.
|
|
11
11
|
|
|
12
|
-
Keys follow the convention "
|
|
12
|
+
Keys follow the convention "BaseSchemaName_fromVersion_toVersion",
|
|
13
13
|
for example "MyEntity_0_1" for the step that migrates MyEntity from version 0 to 1.
|
package/locales/en.json
CHANGED
|
@@ -5,9 +5,16 @@
|
|
|
5
5
|
"propertyNotInSchema": "The property \"{property}\" does not exist in the schema and cannot be used for projection"
|
|
6
6
|
},
|
|
7
7
|
"migrationHelper": {
|
|
8
|
-
"
|
|
8
|
+
"migrateSchemaFailed": "Migration failed for schema \"{schemaName}\".",
|
|
9
9
|
"transformRequiredForProperty": "A transformation function is required to migrate property \"{from}\" to \"{to}\" of type \"{type}\"",
|
|
10
10
|
"coercionProducedUndefined": "Coercion of property \"{property}\" to type \"{type}\" produced undefined but the property is not optional"
|
|
11
11
|
}
|
|
12
|
+
},
|
|
13
|
+
"info": {
|
|
14
|
+
"migrationHelper": {
|
|
15
|
+
"migrateSchemaStarting": "Migrating schema \"{schemaName}\"",
|
|
16
|
+
"migrateSchemaFinalizing": "Finalizing migration of schema \"{schemaName}\"",
|
|
17
|
+
"migrateSchemaComplete": "Migration of schema \"{schemaName}\" complete"
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity-storage-models",
|
|
3
|
-
"version": "0.0.3-next.
|
|
3
|
+
"version": "0.0.3-next.25",
|
|
4
4
|
"description": "Shared models for storage contracts, requests, responses and connector capabilities.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"@twin.org/context": "next",
|
|
18
18
|
"@twin.org/core": "next",
|
|
19
19
|
"@twin.org/entity": "next",
|
|
20
|
+
"@twin.org/logging-models": "next",
|
|
20
21
|
"@twin.org/nameof": "next"
|
|
21
22
|
},
|
|
22
23
|
"main": "./dist/es/index.js",
|