@twin.org/entity-storage-models 0.0.3-next.24 → 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.
Files changed (39) hide show
  1. package/dist/es/helpers/migrationHelper.js +125 -86
  2. package/dist/es/helpers/migrationHelper.js.map +1 -1
  3. package/dist/es/index.js +1 -3
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/models/IEntityStorageMigrationConnector.js.map +1 -1
  6. package/dist/es/models/IMigrationOptions.js.map +1 -1
  7. package/dist/es/models/IResolvedMigrationStep.js.map +1 -1
  8. package/dist/es/models/ISchemaMigration.js.map +1 -1
  9. package/dist/es/models/entityPropertyTransformer.js +2 -0
  10. package/dist/es/models/entityPropertyTransformer.js.map +1 -0
  11. package/dist/types/helpers/migrationHelper.d.ts +34 -26
  12. package/dist/types/index.d.ts +1 -3
  13. package/dist/types/models/IEntityStorageMigrationConnector.d.ts +2 -2
  14. package/dist/types/models/IMigrationOptions.d.ts +6 -21
  15. package/dist/types/models/IResolvedMigrationStep.d.ts +7 -5
  16. package/dist/types/models/ISchemaMigration.d.ts +7 -4
  17. package/dist/types/models/entityPropertyTransformer.d.ts +6 -0
  18. package/docs/changelog.md +7 -0
  19. package/docs/reference/classes/MigrationHelper.md +91 -50
  20. package/docs/reference/index.md +4 -6
  21. package/docs/reference/interfaces/IEntityStorageMigrationConnector.md +2 -2
  22. package/docs/reference/interfaces/IMigrationOptions.md +9 -79
  23. package/docs/reference/interfaces/IResolvedMigrationStep.md +9 -13
  24. package/docs/reference/interfaces/ISchemaMigration.md +9 -12
  25. package/docs/reference/type-aliases/EntityPropertyTransformer.md +34 -0
  26. package/locales/en.json +8 -7
  27. package/package.json +2 -1
  28. package/dist/es/entities/schemaVersion.js +0 -39
  29. package/dist/es/entities/schemaVersion.js.map +0 -1
  30. package/dist/es/schema.js +0 -11
  31. package/dist/es/schema.js.map +0 -1
  32. package/dist/es/services/schemaVersionService.js +0 -265
  33. package/dist/es/services/schemaVersionService.js.map +0 -1
  34. package/dist/types/entities/schemaVersion.d.ts +0 -19
  35. package/dist/types/schema.d.ts +0 -4
  36. package/dist/types/services/schemaVersionService.d.ts +0 -60
  37. package/docs/reference/classes/SchemaVersion.md +0 -39
  38. package/docs/reference/classes/SchemaVersionService.md +0 -130
  39. package/docs/reference/functions/initSchema.md +0 -9
@@ -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 An optional logging component type.
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
@@ -1,6 +1,5 @@
1
1
  // Copyright 2026 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
- export * from "./entities/schemaVersion.js";
4
3
  export * from "./factories/entityStorageConnectorFactory.js";
5
4
  export * from "./factories/schemaMigrationFactory.js";
6
5
  export * from "./helpers/entityStorageHelper.js";
@@ -16,12 +15,11 @@ export * from "./models/api/IEntityStorageRemoveBatchRequest.js";
16
15
  export * from "./models/api/IEntityStorageRemoveRequest.js";
17
16
  export * from "./models/api/IEntityStorageSetBatchRequest.js";
18
17
  export * from "./models/api/IEntityStorageSetRequest.js";
18
+ export * from "./models/entityPropertyTransformer.js";
19
19
  export * from "./models/IEntityStorageComponent.js";
20
20
  export * from "./models/IEntityStorageConnector.js";
21
21
  export * from "./models/IEntityStorageMigrationConnector.js";
22
22
  export * from "./models/IMigrationOptions.js";
23
23
  export * from "./models/IResolvedMigrationStep.js";
24
24
  export * from "./models/ISchemaMigration.js";
25
- export * from "./schema.js";
26
- export * from "./services/schemaVersionService.js";
27
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,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;AAC7C,cAAc,aAAa,CAAC;AAC5B,cAAc,oCAAoC,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./entities/schemaVersion.js\";\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\";\nexport * from \"./schema.js\";\nexport * from \"./services/schemaVersionService.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<T, U>,\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<T, U>,\n\t\tloggingComponentType?: string\n\t): Promise<void>;\n}\n"]}
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\nimport type { IEntitySchemaProperty } from \"@twin.org/entity\";\n\n/**\n * Options controlling how a schema migration is executed.\n */\nexport interface IMigrationOptions<T, U> {\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 * 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?: (\n\t\tschema1Property: IEntitySchemaProperty<T>,\n\t\tschemaProperty2: IEntitySchemaProperty<U>,\n\t\tvalue: unknown\n\t) => unknown;\n\n\t/**\n\t * Called for each partition for progress tracking.\n\t * @param rowTotal The total number of rows to migrate.\n\t * @param rowIndex The number of rows migrated so far.\n\t */\n\tonPartitionProgress?: (rowTotal: number, rowIndex: number) => Promise<void>;\n\n\t/**\n\t * Called for overall progress tracking.\n\t * @param stepKey The key representing the current step in the migration.\n\t * @param itemTotal The total number of items in this progress.\n\t * @param itemIndex The number of items processed so far.\n\t */\n\tonStepProgress?: (stepKey: string, itemTotal: number, itemIndex: number) => Promise<void>;\n}\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 { 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"]}
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 { 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"]}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entityPropertyTransformer.js.map
@@ -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"]}
@@ -1,7 +1,5 @@
1
1
  import { type IEntitySchemaDiff } from "@twin.org/entity";
2
- import type { IEntityStorageConnector } from "../models/IEntityStorageConnector.js";
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
- * 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.
19
- * @param entity The entity to transform.
20
- * @param schemaDiff The schema diff between the old and new schemas.
21
- * @param transformEntityProperty Optional per-property transform hook for object/array properties.
22
- * @returns The transformed entity ready to be written to the new schema.
23
- * @throws GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
24
- * @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
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 applyEntityTransform<T = unknown, U = unknown>(entity: Partial<T>, schemaDiff: IEntitySchemaDiff<T, U>, transformEntityProperty?: IMigrationOptions<T, U>["transformEntityProperty"]): U;
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
- * 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.
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 migrateWithChain(sourceConnector: IEntityStorageMigrationConnector, targetSchemaName: string, steps: IResolvedMigrationStep[], loggingComponentType?: string, batchSize?: number): Promise<{
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
  }
@@ -1,4 +1,3 @@
1
- export * from "./entities/schemaVersion.js";
2
1
  export * from "./factories/entityStorageConnectorFactory.js";
3
2
  export * from "./factories/schemaMigrationFactory.js";
4
3
  export * from "./helpers/entityStorageHelper.js";
@@ -14,11 +13,10 @@ export * from "./models/api/IEntityStorageRemoveBatchRequest.js";
14
13
  export * from "./models/api/IEntityStorageRemoveRequest.js";
15
14
  export * from "./models/api/IEntityStorageSetBatchRequest.js";
16
15
  export * from "./models/api/IEntityStorageSetRequest.js";
16
+ export * from "./models/entityPropertyTransformer.js";
17
17
  export * from "./models/IEntityStorageComponent.js";
18
18
  export * from "./models/IEntityStorageConnector.js";
19
19
  export * from "./models/IEntityStorageMigrationConnector.js";
20
20
  export * from "./models/IMigrationOptions.js";
21
21
  export * from "./models/IResolvedMigrationStep.js";
22
22
  export * from "./models/ISchemaMigration.js";
23
- export * from "./schema.js";
24
- export * from "./services/schemaVersionService.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<T, U>, loggingComponentType?: string): Promise<IEntityStorageConnector<U>>;
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<T, U>, loggingComponentType?: string): Promise<void>;
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<T, U> {
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
- * Optional transformation for properties, usually only called for object and array types.
13
- * @param schema1Property The property schema in the old schema.
14
- * @param schemaProperty2 The property schema in the new schema.
15
- * @param value The value of the property in the old schema.
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
- transformEntityProperty?: (schema1Property: IEntitySchemaProperty<T>, schemaProperty2: IEntitySchemaProperty<U>, value: unknown) => unknown;
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 { IMigrationOptions } from "./IMigrationOptions.js";
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 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.
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?: IMigrationOptions<T, U>["transformEntityProperty"];
37
+ transformEntityProperty?: EntityPropertyTransformer<T, U>;
36
38
  }