@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.
Files changed (31) hide show
  1. package/dist/es/factories/schemaMigrationFactory.js +1 -1
  2. package/dist/es/factories/schemaMigrationFactory.js.map +1 -1
  3. package/dist/es/helpers/migrationHelper.js +125 -86
  4. package/dist/es/helpers/migrationHelper.js.map +1 -1
  5. package/dist/es/index.js +1 -0
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/es/models/IEntityStorageMigrationConnector.js.map +1 -1
  8. package/dist/es/models/IMigrationOptions.js.map +1 -1
  9. package/dist/es/models/IResolvedMigrationStep.js.map +1 -1
  10. package/dist/es/models/ISchemaMigration.js.map +1 -1
  11. package/dist/es/models/entityPropertyTransformer.js +2 -0
  12. package/dist/es/models/entityPropertyTransformer.js.map +1 -0
  13. package/dist/types/factories/schemaMigrationFactory.d.ts +1 -1
  14. package/dist/types/helpers/migrationHelper.d.ts +34 -26
  15. package/dist/types/index.d.ts +1 -0
  16. package/dist/types/models/IEntityStorageMigrationConnector.d.ts +2 -2
  17. package/dist/types/models/IMigrationOptions.d.ts +6 -21
  18. package/dist/types/models/IResolvedMigrationStep.d.ts +7 -5
  19. package/dist/types/models/ISchemaMigration.d.ts +8 -5
  20. package/dist/types/models/entityPropertyTransformer.d.ts +6 -0
  21. package/docs/changelog.md +19 -0
  22. package/docs/reference/classes/MigrationHelper.md +91 -50
  23. package/docs/reference/index.md +4 -0
  24. package/docs/reference/interfaces/IEntityStorageMigrationConnector.md +2 -2
  25. package/docs/reference/interfaces/IMigrationOptions.md +9 -79
  26. package/docs/reference/interfaces/IResolvedMigrationStep.md +9 -13
  27. package/docs/reference/interfaces/ISchemaMigration.md +10 -13
  28. package/docs/reference/type-aliases/EntityPropertyTransformer.md +34 -0
  29. package/docs/reference/variables/SchemaMigrationFactory.md +1 -1
  30. package/locales/en.json +8 -1
  31. 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 "<BaseSchemaName>_<fromVersion>_<toVersion>",
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 \"<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
+ {"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 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
@@ -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";
@@ -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<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"]}
@@ -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 "<BaseSchemaName>_<fromVersion>_<toVersion>",
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 "../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
  }
@@ -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<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
  }
@@ -1,4 +1,4 @@
1
- import type { IMigrationOptions } from "./IMigrationOptions.js";
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 "<BaseSchemaName>_<fromVersion>_<toVersion>"
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 per-property transformer for object/array properties that cannot be
24
- * automatically coerced. T is the source entity type, U is the target entity type.
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?: IMigrationOptions<T, U>["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
- ### applyEntityTransform() {#applyentitytransform}
27
+ ### migrateWithChain() {#migratewithchain}
28
28
 
29
- > `static` **applyEntityTransform**\<`T`, `U`\>(`entity`, `schemaDiff`, `transformEntityProperty?`): `U`
29
+ > `static` **migrateWithChain**(`sourceConnector`, `targetSchemaName`, `steps`, `options?`, `loggingComponentType?`): `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
30
30
 
31
- Applies the entity transformation for a single diff, handling added, removed, and
32
- modified properties according to the provided schema diff and optional transform hook.
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
- #### Type Parameters
36
+ #### Parameters
35
37
 
36
- ##### T
38
+ ##### sourceConnector
37
39
 
38
- `T` = `unknown`
40
+ [`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)
39
41
 
40
- ##### U
42
+ The connector holding data at the stored schema version.
41
43
 
42
- `U` = `unknown`
44
+ ##### targetSchemaName
43
45
 
44
- #### Parameters
46
+ `string`
45
47
 
46
- ##### entity
48
+ The schema name for the current version (used to create the target connector).
47
49
 
48
- `Partial`\<`T`\>
50
+ ##### steps
49
51
 
50
- The entity to transform.
52
+ [`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
51
53
 
52
- ##### schemaDiff
54
+ Ordered, fully-resolved migration steps from stored to current version.
53
55
 
54
- `IEntitySchemaDiff`\<`T`, `U`\>
56
+ ##### options?
55
57
 
56
- The schema diff between the old and new schemas.
58
+ [`IMigrationOptions`](../interfaces/IMigrationOptions.md)
57
59
 
58
- ##### transformEntityProperty?
60
+ Optional migration options.
59
61
 
60
- (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
62
+ ##### loggingComponentType?
61
63
 
62
- Optional per-property transform hook for object/array properties.
64
+ `string`
65
+
66
+ The optional component type to use for logging the migration progress.
63
67
 
64
68
  #### Returns
65
69
 
66
- `U`
70
+ `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
67
71
 
68
- The transformed entity ready to be written to the new schema.
72
+ The finalized connector and the count of migrated entities.
69
73
 
70
- #### Throws
74
+ ***
71
75
 
72
- GeneralError if a transformation is required for an object or array property but no transformEntityProperty function is provided.
76
+ ### migratePartitionWithChain() {#migratepartitionwithchain}
73
77
 
74
- #### Throws
78
+ > `static` **migratePartitionWithChain**(`source`, `target`, `steps`, `options?`): `Promise`\<`number`\>
75
79
 
76
- GeneralError if coercion of a modified property results in undefined for a non-optional target property.
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
- ### migrateWithChain() {#migratewithchain}
113
-
114
- > `static` **migrateWithChain**(`sourceConnector`, `targetSchemaName`, `steps`, `loggingComponentType?`, `batchSize?`): `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
149
+ ### applyEntityTransform() {#applyentitytransform}
115
150
 
116
- Performs a chain migration in a single connector swap, regardless of how many version
117
- steps the chain spans. Creates one target connector, reads all source entities, applies
118
- applyEntityChain to each, writes them to the target, then finalizes the migration.
119
- A chain of one step is equivalent to a traditional single-step migration.
151
+ > `static` **applyEntityTransform**\<`T`, `U`\>(`entity`, `schemaDiff`, `transformEntityProperty?`): `U`
120
152
 
121
- #### Parameters
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
- ##### sourceConnector
156
+ #### Type Parameters
124
157
 
125
- [`IEntityStorageMigrationConnector`](../interfaces/IEntityStorageMigrationConnector.md)
158
+ ##### T
126
159
 
127
- The connector holding data at the stored schema version.
160
+ `T` = `unknown`
128
161
 
129
- ##### targetSchemaName
162
+ ##### U
130
163
 
131
- `string`
164
+ `U` = `unknown`
132
165
 
133
- The schema name for the current version (used to create the target connector).
166
+ #### Parameters
134
167
 
135
- ##### steps
168
+ ##### entity
136
169
 
137
- [`IResolvedMigrationStep`](../interfaces/IResolvedMigrationStep.md)\<`unknown`, `unknown`\>[]
170
+ `Partial`\<`T`\>
138
171
 
139
- Ordered, fully-resolved migration steps from stored to current version.
172
+ The entity to transform.
140
173
 
141
- ##### loggingComponentType?
174
+ ##### schemaDiff
142
175
 
143
- `string`
176
+ `IEntitySchemaDiff`\<`T`, `U`\>
144
177
 
145
- An optional logging component type for connector startup.
178
+ The schema diff between the old and new schemas.
146
179
 
147
- ##### batchSize?
180
+ ##### transformEntityProperty?
148
181
 
149
- `number` = `100`
182
+ [`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
150
183
 
151
- Number of entities to read and write per batch. Defaults to 100.
184
+ Optional per-property transform hook for object/array properties.
152
185
 
153
186
  #### Returns
154
187
 
155
- `Promise`\<\{ `finalConnector`: [`IEntityStorageConnector`](../interfaces/IEntityStorageConnector.md); `migrated`: `number`; \}\>
188
+ `U`
156
189
 
157
- The finalized connector and the count of migrated entities.
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.
@@ -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)\<`T`, `U`\>
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)\<`T`, `U`\>
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\<T, U\>
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
- ### transformEntityProperty? {#transformentityproperty}
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` **onStepProgress?**: (`stepKey`, `itemTotal`, `itemIndex`) => `Promise`\<`void`\>
23
+ > `optional` **onProgress?**: (`progressItem`, `itemTotal`, `itemIndex`) => `Promise`\<`void`\>
94
24
 
95
- Called for overall progress tracking.
25
+ Called for progress tracking.
96
26
 
97
27
  #### Parameters
98
28
 
99
- ##### stepKey
29
+ ##### progressItem
100
30
 
101
- `string`
31
+ `"partitionStart"` \| `"partitionProgress"` \| `"partitionEnd"` \| `"partitionItemsStart"` \| `"partitionItemsProgress"` \| `"partitionItemsEnd"`
102
32
 
103
- The key representing the current step in the migration.
33
+ The item progress being updated.
104
34
 
105
35
  ##### itemTotal
106
36
 
107
37
  `number`
108
38
 
109
- The total number of items in this progress.
39
+ The total number of rows to migrate.
110
40
 
111
41
  ##### itemIndex
112
42
 
113
43
  `number`
114
44
 
115
- The number of items processed so far.
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?**: (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
60
+ > `optional` **transformEntityProperty?**: [`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
61
61
 
62
- Optional per-property transformer for object/array properties that the structural
63
- diff cannot handle automatically. Sourced from an ISchemaMigration override when
64
- one is registered in SchemaMigrationFactory for this step.
62
+ Optional transformation for properties, usually only called for object and array types.
65
63
 
66
- #### Parameters
64
+ #### Param
67
65
 
68
- ##### schema1Property
66
+ The property schema in the old schema.
69
67
 
70
- `IEntitySchemaProperty`\<`T`\>
68
+ #### Param
71
69
 
72
- ##### schemaProperty2
70
+ The property schema in the new schema.
73
71
 
74
- `IEntitySchemaProperty`\<`U`\>
72
+ #### Param
75
73
 
76
- ##### value
77
-
78
- `unknown`
74
+ The value of the property in the old schema.
79
75
 
80
76
  #### Returns
81
77
 
82
- `unknown`
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 "<BaseSchemaName>_<fromVersion>_<toVersion>"
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?**: (`schema1Property`, `schemaProperty2`, `value`) => `unknown`
44
+ > `optional` **transformEntityProperty?**: [`EntityPropertyTransformer`](../type-aliases/EntityPropertyTransformer.md)\<`T`, `U`\>
45
45
 
46
- Optional per-property transformer for object/array properties that cannot be
47
- automatically coerced. T is the source entity type, U is the target entity type.
46
+ Optional transformation for properties, usually only called for object and array types.
48
47
 
49
- #### Parameters
48
+ #### Param
50
49
 
51
- ##### schema1Property
50
+ The property schema in the old schema.
52
51
 
53
- `IEntitySchemaProperty`\<`T`\>
52
+ #### Param
54
53
 
55
- ##### schemaProperty2
54
+ The property schema in the new schema.
56
55
 
57
- `IEntitySchemaProperty`\<`U`\>
56
+ #### Param
58
57
 
59
- ##### value
60
-
61
- `unknown`
58
+ The value of the property in the old schema.
62
59
 
63
60
  #### Returns
64
61
 
65
- `unknown`
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 "<BaseSchemaName>_<fromVersion>_<toVersion>",
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
- "migrationFailed": "Migration failed.",
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.23",
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",