@twin.org/entity-storage-models 0.0.3-next.13 → 0.0.3-next.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/helpers/entityHelper.js +77 -0
- package/dist/es/helpers/entityHelper.js.map +1 -0
- package/dist/es/helpers/migrationHelper.js +220 -0
- package/dist/es/helpers/migrationHelper.js.map +1 -0
- package/dist/es/index.js +5 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IEntityStorageComponent.js.map +1 -1
- package/dist/es/models/IEntityStorageConnector.js.map +1 -1
- package/dist/es/models/IEntityStorageMigrationConnector.js +2 -0
- package/dist/es/models/IEntityStorageMigrationConnector.js.map +1 -0
- package/dist/es/models/IMigrationOptions.js +4 -0
- package/dist/es/models/IMigrationOptions.js.map +1 -0
- package/dist/es/models/api/IEntityStorageCountRequest.js.map +1 -1
- package/dist/es/models/api/IEntityStorageGetRequest.js.map +1 -1
- package/dist/es/models/api/IEntityStorageRemoveRequest.js.map +1 -1
- package/dist/es/models/api/IEntityStorageSetRequest.js.map +1 -1
- package/dist/types/helpers/entityHelper.d.ts +35 -0
- package/dist/types/helpers/migrationHelper.d.ts +61 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/models/IEntityStorageComponent.d.ts +17 -4
- package/dist/types/models/IEntityStorageConnector.d.ts +2 -1
- package/dist/types/models/IEntityStorageMigrationConnector.d.ts +35 -0
- package/dist/types/models/IMigrationOptions.d.ts +32 -0
- package/dist/types/models/api/IEntityStorageCountRequest.d.ts +9 -0
- package/dist/types/models/api/IEntityStorageGetRequest.d.ts +4 -0
- package/dist/types/models/api/IEntityStorageRemoveRequest.d.ts +9 -0
- package/dist/types/models/api/IEntityStorageSetRequest.d.ts +9 -0
- package/docs/changelog.md +14 -0
- package/docs/reference/classes/EntityHelper.md +124 -0
- package/docs/reference/classes/MigrationHelper.md +247 -0
- package/docs/reference/index.md +7 -0
- package/docs/reference/interfaces/IEntityStorageComponent.md +30 -4
- package/docs/reference/interfaces/IEntityStorageConnector.md +13 -1
- package/docs/reference/interfaces/IEntityStorageCountRequest.md +14 -0
- package/docs/reference/interfaces/IEntityStorageGetRequest.md +6 -0
- package/docs/reference/interfaces/IEntityStorageMigrationConnector.md +402 -0
- package/docs/reference/interfaces/IEntityStorageRemoveRequest.md +14 -0
- package/docs/reference/interfaces/IEntityStorageSetRequest.md +14 -0
- package/docs/reference/interfaces/IMigrationOptions.md +119 -0
- package/locales/en.json +7 -1
- package/package.json +2 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright 2026 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { Is, ObjectHelper } from "@twin.org/core";
|
|
4
|
+
import { ComparisonOperator, EntitySchemaHelper } from "@twin.org/entity";
|
|
5
|
+
/**
|
|
6
|
+
* Helper class for performing schema migrations between two connectors.
|
|
7
|
+
*/
|
|
8
|
+
export class EntityHelper {
|
|
9
|
+
/**
|
|
10
|
+
* Runtime name for the class.
|
|
11
|
+
*/
|
|
12
|
+
static CLASS_NAME = "EntityHelper";
|
|
13
|
+
/**
|
|
14
|
+
* Prepare the entity by handling undefined and null values and validating it against the schema.
|
|
15
|
+
* @param entity The entity to handle undefined and null values for.
|
|
16
|
+
* @param schema The schema to validate the entity against.
|
|
17
|
+
* @param additionalProperties Optional list of additional properties to set on the entity.
|
|
18
|
+
* @returns The entity with undefined and null values handled.
|
|
19
|
+
*/
|
|
20
|
+
static prepareEntity(entity, schema, additionalProperties) {
|
|
21
|
+
const entityForValidation = ObjectHelper.clone(entity);
|
|
22
|
+
EntitySchemaHelper.validateEntity(entityForValidation, schema);
|
|
23
|
+
if (Is.arrayValue(schema.properties)) {
|
|
24
|
+
for (const property of schema.properties) {
|
|
25
|
+
if (property.optional ?? false) {
|
|
26
|
+
const propValue = entityForValidation[property.property];
|
|
27
|
+
if (propValue === undefined) {
|
|
28
|
+
ObjectHelper.propertySet(entityForValidation, property.property, null);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (Is.arrayValue(additionalProperties)) {
|
|
34
|
+
for (const additionalProperty of additionalProperties) {
|
|
35
|
+
ObjectHelper.propertySet(entityForValidation, additionalProperty.property, additionalProperty.value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return entityForValidation;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Un-prepare the entity by removing null values.
|
|
42
|
+
* @param entity The entity to handle undefined and null values for.
|
|
43
|
+
* @param removeProperties Optional list of properties to remove from the entity.
|
|
44
|
+
* @returns The entity with undefined and null values handled.
|
|
45
|
+
*/
|
|
46
|
+
static unPrepareEntity(entity, removeProperties) {
|
|
47
|
+
const nonNullEntity = ObjectHelper.removeEmptyProperties(entity, { removeNull: true });
|
|
48
|
+
if (Is.arrayValue(removeProperties)) {
|
|
49
|
+
for (const property of removeProperties) {
|
|
50
|
+
ObjectHelper.propertyDelete(nonNullEntity, property);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return nonNullEntity;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Deep-clone condition tree and map `undefined` to `null` on Equals/NotEquals leaves
|
|
57
|
+
* so in-memory evaluation matches stored-null semantics (optional absent props are stored as null).
|
|
58
|
+
* @param condition The user-supplied condition (not mutated).
|
|
59
|
+
* @returns A clone safe to pass to check.
|
|
60
|
+
*/
|
|
61
|
+
static normalizeConditionValues(condition) {
|
|
62
|
+
if ("conditions" in condition) {
|
|
63
|
+
return {
|
|
64
|
+
...condition,
|
|
65
|
+
conditions: condition.conditions.map(c => EntityHelper.normalizeConditionValues(c))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const leaf = condition;
|
|
69
|
+
if ((leaf.comparison === ComparisonOperator.Equals ||
|
|
70
|
+
leaf.comparison === ComparisonOperator.NotEquals) &&
|
|
71
|
+
leaf.value === undefined) {
|
|
72
|
+
return { ...leaf, value: null };
|
|
73
|
+
}
|
|
74
|
+
return { ...leaf };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=entityHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityHelper.js","sourceRoot":"","sources":["../../../src/helpers/entityHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACN,kBAAkB,EAElB,kBAAkB,EAElB,MAAM,kBAAkB,CAAC;AAG1B;;GAEG;AACH,MAAM,OAAO,YAAY;IACxB;;OAEG;IACI,MAAM,CAAU,UAAU,kBAAkC;IAEnE;;;;;;OAMG;IACI,MAAM,CAAC,aAAa,CAC1B,MAAS,EACT,MAAwB,EACxB,oBAA6D;QAE7D,MAAM,mBAAmB,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvD,kBAAkB,CAAC,cAAc,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QAE/D,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YACtC,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1C,IAAI,QAAQ,CAAC,QAAQ,IAAI,KAAK,EAAE,CAAC;oBAChC,MAAM,SAAS,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;oBACzD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;wBAC7B,YAAY,CAAC,WAAW,CAAC,mBAAmB,EAAE,QAAQ,CAAC,QAAkB,EAAE,IAAI,CAAC,CAAC;oBAClF,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,IAAI,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACzC,KAAK,MAAM,kBAAkB,IAAI,oBAAoB,EAAE,CAAC;gBACvD,YAAY,CAAC,WAAW,CACvB,mBAAmB,EACnB,kBAAkB,CAAC,QAAQ,EAC3B,kBAAkB,CAAC,KAAK,CACxB,CAAC;YACH,CAAC;QACF,CAAC;QAED,OAAO,mBAAmB,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,eAAe,CAAI,MAA8B,EAAE,gBAA2B;QAC3F,MAAM,aAAa,GAAG,YAAY,CAAC,qBAAqB,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvF,IAAI,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACrC,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;gBACzC,YAAY,CAAC,cAAc,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;YACtD,CAAC;QACF,CAAC;QAED,OAAO,aAAkB,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,wBAAwB,CAAI,SAA6B;QACtE,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;YAC/B,OAAO;gBACN,GAAG,SAAS;gBACZ,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC;aACnF,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC;QACvB,IACC,CAAC,IAAI,CAAC,UAAU,KAAK,kBAAkB,CAAC,MAAM;YAC7C,IAAI,CAAC,UAAU,KAAK,kBAAkB,CAAC,SAAS,CAAC;YAClD,IAAI,CAAC,KAAK,KAAK,SAAS,EACvB,CAAC;YACF,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;IACpB,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\ttype EntityCondition,\n\tEntitySchemaHelper,\n\ttype IEntitySchema\n} from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\n\n/**\n * Helper class for performing schema migrations between two connectors.\n */\nexport class EntityHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityHelper>();\n\n\t/**\n\t * Prepare the entity by handling undefined and null values and validating it against the schema.\n\t * @param entity The entity to handle undefined and null values for.\n\t * @param schema The schema to validate the entity against.\n\t * @param additionalProperties Optional list of additional properties to set on the entity.\n\t * @returns The entity with undefined and null values handled.\n\t */\n\tpublic static prepareEntity<T>(\n\t\tentity: T,\n\t\tschema: IEntitySchema<T>,\n\t\tadditionalProperties?: { property: string; value: unknown }[]\n\t): T {\n\t\tconst entityForValidation = ObjectHelper.clone(entity);\n\t\tEntitySchemaHelper.validateEntity(entityForValidation, schema);\n\n\t\tif (Is.arrayValue(schema.properties)) {\n\t\t\tfor (const property of schema.properties) {\n\t\t\t\tif (property.optional ?? false) {\n\t\t\t\t\tconst propValue = entityForValidation[property.property];\n\t\t\t\t\tif (propValue === undefined) {\n\t\t\t\t\t\tObjectHelper.propertySet(entityForValidation, property.property as string, null);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Is.arrayValue(additionalProperties)) {\n\t\t\tfor (const additionalProperty of additionalProperties) {\n\t\t\t\tObjectHelper.propertySet(\n\t\t\t\t\tentityForValidation,\n\t\t\t\t\tadditionalProperty.property,\n\t\t\t\t\tadditionalProperty.value\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn entityForValidation;\n\t}\n\n\t/**\n\t * Un-prepare the entity by removing null values.\n\t * @param entity The entity to handle undefined and null values for.\n\t * @param removeProperties Optional list of properties to remove from the entity.\n\t * @returns The entity with undefined and null values handled.\n\t */\n\tpublic static unPrepareEntity<T>(entity: Partial<T> | undefined, removeProperties?: string[]): T {\n\t\tconst nonNullEntity = ObjectHelper.removeEmptyProperties(entity, { removeNull: true });\n\n\t\tif (Is.arrayValue(removeProperties)) {\n\t\t\tfor (const property of removeProperties) {\n\t\t\t\tObjectHelper.propertyDelete(nonNullEntity, property);\n\t\t\t}\n\t\t}\n\n\t\treturn nonNullEntity as T;\n\t}\n\n\t/**\n\t * Deep-clone condition tree and map `undefined` to `null` on Equals/NotEquals leaves\n\t * so in-memory evaluation matches stored-null semantics (optional absent props are stored as null).\n\t * @param condition The user-supplied condition (not mutated).\n\t * @returns A clone safe to pass to check.\n\t */\n\tpublic static normalizeConditionValues<T>(condition: EntityCondition<T>): EntityCondition<T> {\n\t\tif (\"conditions\" in condition) {\n\t\t\treturn {\n\t\t\t\t...condition,\n\t\t\t\tconditions: condition.conditions.map(c => EntityHelper.normalizeConditionValues(c))\n\t\t\t};\n\t\t}\n\n\t\tconst leaf = condition;\n\t\tif (\n\t\t\t(leaf.comparison === ComparisonOperator.Equals ||\n\t\t\t\tleaf.comparison === ComparisonOperator.NotEquals) &&\n\t\t\tleaf.value === undefined\n\t\t) {\n\t\t\treturn { ...leaf, value: null };\n\t\t}\n\t\treturn { ...leaf };\n\t}\n}\n"]}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Copyright 2026 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { ContextIdStore } from "@twin.org/context";
|
|
4
|
+
import { Coerce, GeneralError, Is, ObjectHelper } from "@twin.org/core";
|
|
5
|
+
import { EntitySchemaDiffHelper, EntitySchemaPropertyType } from "@twin.org/entity";
|
|
6
|
+
/**
|
|
7
|
+
* Helper class for performing schema migrations between two connectors.
|
|
8
|
+
*/
|
|
9
|
+
export class MigrationHelper {
|
|
10
|
+
/**
|
|
11
|
+
* Runtime name for the class.
|
|
12
|
+
*/
|
|
13
|
+
static CLASS_NAME = "MigrationHelper";
|
|
14
|
+
/**
|
|
15
|
+
* Performs a migration between two connectors, using the provided options and schema diff to control the migration behaviour.
|
|
16
|
+
* @param sourceConnector The connector to migrate from to allow the migration helper to create the new connector and finalize the migration.
|
|
17
|
+
* @param targetEntitySchemaName The name of the new entity schema.
|
|
18
|
+
* @param renames An optional list of property renames to apply during migration.
|
|
19
|
+
* @param options Options controlling migration behaviour.
|
|
20
|
+
* @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.
|
|
21
|
+
* @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.
|
|
22
|
+
*/
|
|
23
|
+
static async migrate(sourceConnector, targetEntitySchemaName, renames, options, loggingComponentType) {
|
|
24
|
+
let targetConnector;
|
|
25
|
+
try {
|
|
26
|
+
// We use the migration method to create the new connector as it will use a temporary storage location if necessary
|
|
27
|
+
targetConnector = await sourceConnector.createTargetConnector(targetEntitySchemaName);
|
|
28
|
+
// Startup both connectors to ensure they are ready for the migration, this will call bootstrap and start if they are defined.
|
|
29
|
+
await MigrationHelper.startupConnector(sourceConnector, loggingComponentType);
|
|
30
|
+
await MigrationHelper.startupConnector(targetConnector, loggingComponentType);
|
|
31
|
+
// Get the unique partition context ids to run the migration for each partition.
|
|
32
|
+
let partitionContextIds = await sourceConnector.getPartitionContextIds();
|
|
33
|
+
// If there are no partitions, we still want to run the migration once to handle the schema changes,
|
|
34
|
+
// so we create a single empty context for the migration to run in.
|
|
35
|
+
if (!Is.arrayValue(partitionContextIds)) {
|
|
36
|
+
partitionContextIds ??= [];
|
|
37
|
+
partitionContextIds.push({});
|
|
38
|
+
}
|
|
39
|
+
// Get the schemas
|
|
40
|
+
const sourceSchema = sourceConnector.getSchema();
|
|
41
|
+
const targetSchema = targetConnector.getSchema();
|
|
42
|
+
// Get the schema diff between the source and target schemas.
|
|
43
|
+
const schemaDiff = EntitySchemaDiffHelper.diff(sourceSchema.properties ?? [], targetSchema.properties ?? [], renames);
|
|
44
|
+
// Only perform a migration if the schemas have changed
|
|
45
|
+
if (EntitySchemaDiffHelper.hasChanges(schemaDiff)) {
|
|
46
|
+
// Perform the migration, which will read from the current store and write to the target connector's store.
|
|
47
|
+
const migrated = await MigrationHelper.migrateEntities(sourceConnector, targetConnector, partitionContextIds, schemaDiff, options);
|
|
48
|
+
// The migration is now complete, finalize the migration which could entail
|
|
49
|
+
// renaming of resources etc, determined by the implementation of finalizeMigration.
|
|
50
|
+
// it also needs to stop and teardown any resources no longer in use after the migration,
|
|
51
|
+
// such as the old connector and its underlying storage.
|
|
52
|
+
const finalConnector = await sourceConnector.finalizeMigration(targetConnector, options, loggingComponentType);
|
|
53
|
+
return {
|
|
54
|
+
finalConnector,
|
|
55
|
+
migrated
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
finalConnector: undefined,
|
|
60
|
+
migrated: 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
await sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);
|
|
65
|
+
throw new GeneralError(MigrationHelper.CLASS_NAME, "migrationFailed", undefined, error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generic per-partition migration loop.
|
|
70
|
+
* @param source Connector to read from (current schema, already bootstrapped).
|
|
71
|
+
* @param target Connector to write to (new schema, already bootstrapped).
|
|
72
|
+
* @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.
|
|
73
|
+
* @param schemaDiff The schema diff.
|
|
74
|
+
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
75
|
+
* @returns The number of entities successfully migrated.
|
|
76
|
+
*/
|
|
77
|
+
static async migrateEntities(source, target, partitionContextIds, schemaDiff, options) {
|
|
78
|
+
let migrated = 0;
|
|
79
|
+
await options?.onStepProgress?.("migrationStart", 0, 0);
|
|
80
|
+
const effectivePartitions = partitionContextIds.length > 0 ? partitionContextIds : [{}];
|
|
81
|
+
for (let i = 0; i < effectivePartitions.length; i++) {
|
|
82
|
+
await ContextIdStore.run(effectivePartitions[i], async () => {
|
|
83
|
+
await options?.onStepProgress?.("partitionStart", effectivePartitions.length, i);
|
|
84
|
+
migrated += await MigrationHelper.migratePartition(source, target, effectivePartitions.length, i, schemaDiff, options);
|
|
85
|
+
await options?.onStepProgress?.("partitionEnd", effectivePartitions.length, i);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
await options?.onStepProgress?.("migrationEnd", 0, 0);
|
|
89
|
+
return migrated;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generic per-partition migration loop.
|
|
93
|
+
* @param source Connector to read from (current schema, already bootstrapped).
|
|
94
|
+
* @param target Connector to write to (new schema, already bootstrapped).
|
|
95
|
+
* @param partitionTotal The total number of partitions to migrate, used for progress reporting.
|
|
96
|
+
* @param partitionIndex The index of the current partition being migrated, used for progress reporting.
|
|
97
|
+
* @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.
|
|
98
|
+
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
99
|
+
* @returns The number of entities successfully migrated.
|
|
100
|
+
*/
|
|
101
|
+
static async migratePartition(source, target, partitionTotal, partitionIndex, schemaDiff, options) {
|
|
102
|
+
const batchSize = options?.batchSize ?? 100;
|
|
103
|
+
let migrated = 0;
|
|
104
|
+
let cursor;
|
|
105
|
+
const totalEntities = await source.count();
|
|
106
|
+
await options?.onPartitionProgress?.(totalEntities, 0);
|
|
107
|
+
if (totalEntities > 0) {
|
|
108
|
+
do {
|
|
109
|
+
const page = await source.query(undefined, undefined, undefined, cursor, batchSize);
|
|
110
|
+
cursor = page.cursor;
|
|
111
|
+
if (Is.arrayValue(page.entities)) {
|
|
112
|
+
const transformedBatch = [];
|
|
113
|
+
for (const entity of page.entities) {
|
|
114
|
+
const transformedEntity = MigrationHelper.applyEntityTransform(entity, schemaDiff, options);
|
|
115
|
+
transformedBatch.push(transformedEntity);
|
|
116
|
+
}
|
|
117
|
+
await target.setBatch(transformedBatch);
|
|
118
|
+
migrated += transformedBatch.length;
|
|
119
|
+
}
|
|
120
|
+
await options?.onPartitionProgress?.(totalEntities, migrated);
|
|
121
|
+
} while (Is.stringValue(cursor));
|
|
122
|
+
}
|
|
123
|
+
return migrated;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Applies the entity transformation for migration, using the provided options and schema diff.
|
|
127
|
+
* @param entity The entity to transform.
|
|
128
|
+
* @param schemaDiff The schema diff between the old and new schemas.
|
|
129
|
+
* @param options The migration options.
|
|
130
|
+
* @returns The transformed entity ready to be written to the new schema.
|
|
131
|
+
* @throws GeneralError if a transformation is required for an object or array property but no `options.transformEntityProperty` function is provided.
|
|
132
|
+
* @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
133
|
+
*/
|
|
134
|
+
static applyEntityTransform(entity, schemaDiff, options) {
|
|
135
|
+
const newEntity = {};
|
|
136
|
+
for (const property of schemaDiff.unchanged) {
|
|
137
|
+
ObjectHelper.propertySet(newEntity, property.property, ObjectHelper.propertyGet(entity, property.property));
|
|
138
|
+
}
|
|
139
|
+
for (const change of schemaDiff.added) {
|
|
140
|
+
let defValue;
|
|
141
|
+
if (!(change.optional ?? false)) {
|
|
142
|
+
if (change.type === EntitySchemaPropertyType.Boolean) {
|
|
143
|
+
defValue = false;
|
|
144
|
+
}
|
|
145
|
+
else if (change.type === EntitySchemaPropertyType.Number ||
|
|
146
|
+
change.type === EntitySchemaPropertyType.Integer) {
|
|
147
|
+
defValue = 0;
|
|
148
|
+
}
|
|
149
|
+
else if (change.type === EntitySchemaPropertyType.String) {
|
|
150
|
+
defValue = "";
|
|
151
|
+
}
|
|
152
|
+
else if (change.type === EntitySchemaPropertyType.Array) {
|
|
153
|
+
defValue = [];
|
|
154
|
+
}
|
|
155
|
+
else if (change.type === EntitySchemaPropertyType.Object) {
|
|
156
|
+
defValue = {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (Is.notEmpty(defValue)) {
|
|
160
|
+
ObjectHelper.propertySet(newEntity, change.property, defValue);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const change of schemaDiff.modified) {
|
|
164
|
+
const currentValue = ObjectHelper.propertyGet(entity, change.from.property);
|
|
165
|
+
let newValue;
|
|
166
|
+
if (change.to.type === EntitySchemaPropertyType.Boolean) {
|
|
167
|
+
newValue = Coerce.boolean(currentValue);
|
|
168
|
+
}
|
|
169
|
+
else if (change.to.type === EntitySchemaPropertyType.Number ||
|
|
170
|
+
change.to.type === EntitySchemaPropertyType.Integer) {
|
|
171
|
+
newValue = Coerce.number(currentValue);
|
|
172
|
+
}
|
|
173
|
+
else if (change.to.type === EntitySchemaPropertyType.String) {
|
|
174
|
+
newValue = Coerce.string(currentValue);
|
|
175
|
+
}
|
|
176
|
+
else if (change.to.type === EntitySchemaPropertyType.Array ||
|
|
177
|
+
change.to.type === EntitySchemaPropertyType.Object) {
|
|
178
|
+
if (!Is.function(options?.transformEntityProperty)) {
|
|
179
|
+
throw new GeneralError(MigrationHelper.CLASS_NAME, "transformRequiredForProperty", {
|
|
180
|
+
from: change.from.property,
|
|
181
|
+
to: change.to.property,
|
|
182
|
+
type: change.from.type
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
newValue = options.transformEntityProperty(change.from, change.to, currentValue);
|
|
186
|
+
}
|
|
187
|
+
if (newValue === undefined && !(change.to.optional ?? false)) {
|
|
188
|
+
throw new GeneralError(MigrationHelper.CLASS_NAME, "coercionProducedUndefined", {
|
|
189
|
+
property: change.to.property,
|
|
190
|
+
type: change.to.type
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (Is.notEmpty(newValue)) {
|
|
194
|
+
ObjectHelper.propertySet(newEntity, change.to.property, newValue);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Removed properties are simply dropped.
|
|
198
|
+
// for (const change of schemaDiff.removed) {
|
|
199
|
+
// }
|
|
200
|
+
return newEntity;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Startup the connector by calling bootstrap and start if they are defined.
|
|
204
|
+
* @param connector The connector to startup.
|
|
205
|
+
* @param loggingComponentType An optional logging component type to use for bootstrapping and starting the connector.
|
|
206
|
+
* @returns Nothing.
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
static async startupConnector(connector, loggingComponentType) {
|
|
210
|
+
const bootstrap = connector.bootstrap?.bind(connector);
|
|
211
|
+
if (Is.function(bootstrap)) {
|
|
212
|
+
await bootstrap(loggingComponentType);
|
|
213
|
+
}
|
|
214
|
+
const start = connector.start?.bind(connector);
|
|
215
|
+
if (Is.function(start)) {
|
|
216
|
+
await start(loggingComponentType);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=migrationHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrationHelper.js","sourceRoot":"","sources":["../../../src/helpers/migrationHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport { Coerce, GeneralError, Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tEntitySchemaDiffHelper,\n\tEntitySchemaPropertyType,\n\ttype IEntitySchemaDiff\n} from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntityStorageConnector } from \"../models/IEntityStorageConnector.js\";\nimport type { IEntityStorageMigrationConnector } from \"../models/IEntityStorageMigrationConnector.js\";\nimport type { IMigrationOptions } from \"../models/IMigrationOptions.js\";\n\n/**\n * Helper class for performing schema migrations between two connectors.\n */\nexport class MigrationHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<MigrationHelper>();\n\n\t/**\n\t * Performs a migration between two connectors, using the provided options and schema diff to control the migration behaviour.\n\t * @param sourceConnector The connector to migrate from to allow the migration helper to create the new connector and finalize the migration.\n\t * @param targetEntitySchemaName The name of the new entity schema.\n\t * @param renames An optional list of property renames to apply during migration.\n\t * @param options Options controlling migration behaviour.\n\t * @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.\n\t * @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.\n\t */\n\tpublic static async migrate<T, U>(\n\t\tsourceConnector: IEntityStorageMigrationConnector<T>,\n\t\ttargetEntitySchemaName: string,\n\t\trenames?: { from: string; to: string }[],\n\t\toptions?: IMigrationOptions<T, U>,\n\t\tloggingComponentType?: string\n\t): Promise<{\n\t\tfinalConnector?: IEntityStorageConnector<U>;\n\t\tmigrated: number;\n\t}> {\n\t\tlet targetConnector: IEntityStorageConnector<U> | undefined;\n\t\ttry {\n\t\t\t// We use the migration method to create the new connector as it will use a temporary storage location if necessary\n\t\t\ttargetConnector = await sourceConnector.createTargetConnector<U>(targetEntitySchemaName);\n\n\t\t\t// Startup both connectors to ensure they are ready for the migration, this will call bootstrap and start if they are defined.\n\t\t\tawait MigrationHelper.startupConnector<T>(sourceConnector, loggingComponentType);\n\t\t\tawait MigrationHelper.startupConnector<U>(targetConnector, loggingComponentType);\n\n\t\t\t// Get the unique partition context ids to run the migration for each partition.\n\t\t\tlet partitionContextIds = await sourceConnector.getPartitionContextIds();\n\n\t\t\t// If there are no partitions, we still want to run the migration once to handle the schema changes,\n\t\t\t// so we create a single empty context for the migration to run in.\n\t\t\tif (!Is.arrayValue(partitionContextIds)) {\n\t\t\t\tpartitionContextIds ??= [];\n\t\t\t\tpartitionContextIds.push({});\n\t\t\t}\n\n\t\t\t// Get the schemas\n\t\t\tconst sourceSchema = sourceConnector.getSchema();\n\t\t\tconst targetSchema = targetConnector.getSchema();\n\n\t\t\t// Get the schema diff between the source and target schemas.\n\t\t\tconst schemaDiff = EntitySchemaDiffHelper.diff<T, U>(\n\t\t\t\tsourceSchema.properties ?? [],\n\t\t\t\ttargetSchema.properties ?? [],\n\t\t\t\trenames\n\t\t\t);\n\n\t\t\t// Only perform a migration if the schemas have changed\n\t\t\tif (EntitySchemaDiffHelper.hasChanges(schemaDiff)) {\n\t\t\t\t// Perform the migration, which will read from the current store and write to the target connector's store.\n\t\t\t\tconst migrated = await MigrationHelper.migrateEntities<T, U>(\n\t\t\t\t\tsourceConnector,\n\t\t\t\t\ttargetConnector,\n\t\t\t\t\tpartitionContextIds,\n\t\t\t\t\tschemaDiff,\n\t\t\t\t\toptions\n\t\t\t\t);\n\n\t\t\t\t// The migration is now complete, finalize the migration which could entail\n\t\t\t\t// renaming of resources etc, determined by the implementation of finalizeMigration.\n\t\t\t\t// it also needs to stop and teardown any resources no longer in use after the migration,\n\t\t\t\t// such as the old connector and its underlying storage.\n\t\t\t\tconst finalConnector = await sourceConnector.finalizeMigration(\n\t\t\t\t\ttargetConnector,\n\t\t\t\t\toptions,\n\t\t\t\t\tloggingComponentType\n\t\t\t\t);\n\n\t\t\t\treturn {\n\t\t\t\t\tfinalConnector,\n\t\t\t\t\tmigrated\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tfinalConnector: undefined,\n\t\t\t\tmigrated: 0\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tawait sourceConnector.cleanupMigration(targetConnector, options, loggingComponentType);\n\n\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"migrationFailed\", undefined, error);\n\t\t}\n\t}\n\n\t/**\n\t * Generic per-partition migration loop.\n\t * @param source Connector to read from (current schema, already bootstrapped).\n\t * @param target Connector to write to (new schema, already bootstrapped).\n\t * @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.\n\t * @param schemaDiff The schema diff.\n\t * @param options Optional migration controls (batchSize, transformEntity, onProgress).\n\t * @returns The number of entities successfully migrated.\n\t */\n\tpublic static async migrateEntities<T = unknown, U = T>(\n\t\tsource: IEntityStorageMigrationConnector<T>,\n\t\ttarget: IEntityStorageConnector<U>,\n\t\tpartitionContextIds: IContextIds[],\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): Promise<number> {\n\t\tlet migrated = 0;\n\n\t\tawait options?.onStepProgress?.(\"migrationStart\", 0, 0);\n\n\t\tconst effectivePartitions: IContextIds[] =\n\t\t\tpartitionContextIds.length > 0 ? partitionContextIds : [{}];\n\n\t\tfor (let i = 0; i < effectivePartitions.length; i++) {\n\t\t\tawait ContextIdStore.run(effectivePartitions[i], async () => {\n\t\t\t\tawait options?.onStepProgress?.(\"partitionStart\", effectivePartitions.length, i);\n\n\t\t\t\tmigrated += await MigrationHelper.migratePartition(\n\t\t\t\t\tsource,\n\t\t\t\t\ttarget,\n\t\t\t\t\teffectivePartitions.length,\n\t\t\t\t\ti,\n\t\t\t\t\tschemaDiff,\n\t\t\t\t\toptions\n\t\t\t\t);\n\n\t\t\t\tawait options?.onStepProgress?.(\"partitionEnd\", effectivePartitions.length, i);\n\t\t\t});\n\t\t}\n\n\t\tawait options?.onStepProgress?.(\"migrationEnd\", 0, 0);\n\n\t\treturn migrated;\n\t}\n\n\t/**\n\t * Generic per-partition migration loop.\n\t * @param source Connector to read from (current schema, already bootstrapped).\n\t * @param target Connector to write to (new schema, already bootstrapped).\n\t * @param partitionTotal The total number of partitions to migrate, used for progress reporting.\n\t * @param partitionIndex The index of the current partition being migrated, used for progress reporting.\n\t * @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.\n\t * @param options Optional migration controls (batchSize, transformEntity, onProgress).\n\t * @returns The number of entities successfully migrated.\n\t */\n\tpublic static async migratePartition<T = unknown, U = unknown>(\n\t\tsource: IEntityStorageConnector<T>,\n\t\ttarget: IEntityStorageConnector<U>,\n\t\tpartitionTotal: number,\n\t\tpartitionIndex: number,\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): Promise<number> {\n\t\tconst batchSize = options?.batchSize ?? 100;\n\t\tlet migrated = 0;\n\t\tlet cursor: string | undefined;\n\n\t\tconst totalEntities = await source.count();\n\n\t\tawait options?.onPartitionProgress?.(totalEntities, 0);\n\n\t\tif (totalEntities > 0) {\n\t\t\tdo {\n\t\t\t\tconst page = await source.query(undefined, undefined, undefined, cursor, batchSize);\n\t\t\t\tcursor = page.cursor;\n\n\t\t\t\tif (Is.arrayValue(page.entities)) {\n\t\t\t\t\tconst transformedBatch: U[] = [];\n\n\t\t\t\t\tfor (const entity of page.entities) {\n\t\t\t\t\t\tconst transformedEntity = MigrationHelper.applyEntityTransform<T, U>(\n\t\t\t\t\t\t\tentity,\n\t\t\t\t\t\t\tschemaDiff,\n\t\t\t\t\t\t\toptions\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttransformedBatch.push(transformedEntity);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait target.setBatch(transformedBatch);\n\t\t\t\t\tmigrated += transformedBatch.length;\n\t\t\t\t}\n\n\t\t\t\tawait options?.onPartitionProgress?.(totalEntities, migrated);\n\t\t\t} while (Is.stringValue(cursor));\n\t\t}\n\n\t\treturn migrated;\n\t}\n\n\t/**\n\t * Applies the entity transformation for migration, using the provided options and schema diff.\n\t * @param entity The entity to transform.\n\t * @param schemaDiff The schema diff between the old and new schemas.\n\t * @param options The migration options.\n\t * @returns The transformed entity ready to be written to the new schema.\n\t * @throws GeneralError if a transformation is required for an object or array property but no `options.transformEntityProperty` function is provided.\n\t * @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.\n\t */\n\tpublic static applyEntityTransform<T = unknown, U = unknown>(\n\t\tentity: Partial<T>,\n\t\tschemaDiff: IEntitySchemaDiff<T, U>,\n\t\toptions?: IMigrationOptions<T, U>\n\t): U {\n\t\tconst newEntity = {} as U;\n\n\t\tfor (const property of schemaDiff.unchanged) {\n\t\t\tObjectHelper.propertySet(\n\t\t\t\tnewEntity,\n\t\t\t\tproperty.property as string,\n\t\t\t\tObjectHelper.propertyGet(entity, property.property as string)\n\t\t\t);\n\t\t}\n\n\t\tfor (const change of schemaDiff.added) {\n\t\t\tlet defValue;\n\n\t\t\tif (!(change.optional ?? false)) {\n\t\t\t\tif (change.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\t\tdefValue = false;\n\t\t\t\t} else if (\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Number ||\n\t\t\t\t\tchange.type === EntitySchemaPropertyType.Integer\n\t\t\t\t) {\n\t\t\t\t\tdefValue = 0;\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.String) {\n\t\t\t\t\tdefValue = \"\";\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Array) {\n\t\t\t\t\tdefValue = [];\n\t\t\t\t} else if (change.type === EntitySchemaPropertyType.Object) {\n\t\t\t\t\tdefValue = {};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(defValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.property as string, defValue);\n\t\t\t}\n\t\t}\n\n\t\tfor (const change of schemaDiff.modified) {\n\t\t\tconst currentValue = ObjectHelper.propertyGet(entity, change.from.property as string);\n\t\t\tlet newValue;\n\n\t\t\tif (change.to.type === EntitySchemaPropertyType.Boolean) {\n\t\t\t\tnewValue = Coerce.boolean(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Number ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Integer\n\t\t\t) {\n\t\t\t\tnewValue = Coerce.number(currentValue);\n\t\t\t} else if (change.to.type === EntitySchemaPropertyType.String) {\n\t\t\t\tnewValue = Coerce.string(currentValue);\n\t\t\t} else if (\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Array ||\n\t\t\t\tchange.to.type === EntitySchemaPropertyType.Object\n\t\t\t) {\n\t\t\t\tif (!Is.function(options?.transformEntityProperty)) {\n\t\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"transformRequiredForProperty\", {\n\t\t\t\t\t\tfrom: change.from.property,\n\t\t\t\t\t\tto: change.to.property,\n\t\t\t\t\t\ttype: change.from.type\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tnewValue = options.transformEntityProperty(change.from, change.to, currentValue);\n\t\t\t}\n\n\t\t\tif (newValue === undefined && !(change.to.optional ?? false)) {\n\t\t\t\tthrow new GeneralError(MigrationHelper.CLASS_NAME, \"coercionProducedUndefined\", {\n\t\t\t\t\tproperty: change.to.property,\n\t\t\t\t\ttype: change.to.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (Is.notEmpty(newValue)) {\n\t\t\t\tObjectHelper.propertySet(newEntity, change.to.property as string, newValue);\n\t\t\t}\n\t\t}\n\n\t\t// Removed properties are simply dropped.\n\t\t// for (const change of schemaDiff.removed) {\n\t\t// }\n\n\t\treturn newEntity;\n\t}\n\n\t/**\n\t * Startup the connector by calling bootstrap and start if they are defined.\n\t * @param connector The connector to startup.\n\t * @param loggingComponentType An optional logging component type to use for bootstrapping and starting the connector.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate static async startupConnector<T>(\n\t\tconnector: IEntityStorageConnector<T>,\n\t\tloggingComponentType: string | undefined\n\t): Promise<void> {\n\t\tconst bootstrap = connector.bootstrap?.bind(connector);\n\t\tif (Is.function(bootstrap)) {\n\t\t\tawait bootstrap(loggingComponentType);\n\t\t}\n\t\tconst start = connector.start?.bind(connector);\n\t\tif (Is.function(start)) {\n\t\t\tawait start(loggingComponentType);\n\t\t}\n\t}\n}\n"]}
|
package/dist/es/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
export * from "./factories/entityStorageConnectorFactory.js";
|
|
4
|
+
export * from "./helpers/entityHelper.js";
|
|
5
|
+
export * from "./helpers/migrationHelper.js";
|
|
4
6
|
export * from "./models/api/IEntityStorageCountRequest.js";
|
|
5
|
-
export * from "./models/api/IEntityStorageEmptyRequest.js";
|
|
6
7
|
export * from "./models/api/IEntityStorageCountResponse.js";
|
|
8
|
+
export * from "./models/api/IEntityStorageEmptyRequest.js";
|
|
7
9
|
export * from "./models/api/IEntityStorageGetRequest.js";
|
|
8
10
|
export * from "./models/api/IEntityStorageGetResponse.js";
|
|
9
11
|
export * from "./models/api/IEntityStorageListRequest.js";
|
|
@@ -14,4 +16,6 @@ export * from "./models/api/IEntityStorageSetBatchRequest.js";
|
|
|
14
16
|
export * from "./models/api/IEntityStorageSetRequest.js";
|
|
15
17
|
export * from "./models/IEntityStorageComponent.js";
|
|
16
18
|
export * from "./models/IEntityStorageConnector.js";
|
|
19
|
+
export * from "./models/IEntityStorageMigrationConnector.js";
|
|
20
|
+
export * from "./models/IMigrationOptions.js";
|
|
17
21
|
//# sourceMappingURL=index.js.map
|
package/dist/es/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+BAA+B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./factories/entityStorageConnectorFactory.js\";\nexport * from \"./helpers/entityHelper.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\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageComponent.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageComponent.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IComponent } from \"@twin.org/core\";\nimport type { EntityCondition, SortDirection } from \"@twin.org/entity\";\n\n/**\n * Interface describing an entity storage component.\n */\nexport interface IEntityStorageComponent<T = unknown> extends IComponent {\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @returns The id of the entity.\n\t */\n\tset(entity: T): Promise<void>;\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tsetBatch(entities: T[]): Promise<void>;\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get, or the index value if secondaryIndex is set.\n\t * @param secondaryIndex Get the item using a secondary index.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tget(
|
|
1
|
+
{"version":3,"file":"IEntityStorageComponent.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageComponent.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IComponent } from \"@twin.org/core\";\nimport type { EntityCondition, SortDirection } from \"@twin.org/entity\";\n\n/**\n * Interface describing an entity storage component.\n */\nexport interface IEntityStorageComponent<T = unknown> extends IComponent {\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The id of the entity.\n\t */\n\tset(entity: T, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tsetBatch(entities: T[]): Promise<void>;\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get, or the index value if secondaryIndex is set.\n\t * @param secondaryIndex Get the item using a secondary index.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tget(\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<T | undefined>;\n\n\t/**\n\t * Remove the entity.\n\t * @param id The id of the entity to remove.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns Nothing.\n\t */\n\tremove(id: string, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Remove multiple entities by id.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tremoveBatch(ids: string[]): Promise<void>;\n\n\t/**\n\t * Query all the entities which match the conditions.\n\t * @param conditions The conditions to match for the entities.\n\t * @param orderBy The order for the results.\n\t * @param orderByDirection The direction for the order, defaults to ascending.\n\t * @param properties The optional properties to return, defaults to all.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t */\n\tquery(\n\t\tconditions?: EntityCondition<T>,\n\t\torderBy?: keyof T,\n\t\torderByDirection?: SortDirection,\n\t\tproperties?: (keyof T)[],\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The entities, which can be partial if a limited keys list was provided.\n\t\t */\n\t\tentities: Partial<T>[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more entities.\n\t\t */\n\t\tcursor?: string;\n\t}>;\n\n\t/**\n\t * Remove all entities from the storage.\n\t * @returns Nothing.\n\t */\n\tempty(): Promise<void>;\n\n\t/**\n\t * Count all the entities which match the conditions.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The total count of entities in the storage.\n\t */\n\tcount(conditions?: EntityCondition<T>): Promise<number>;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageConnector.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageConnector.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IComponent } from \"@twin.org/core\";\nimport type { EntityCondition, IEntitySchema, SortDirection } from \"@twin.org/entity\";\n\n/**\n * Interface describing an entity storage connector.\n */\nexport interface IEntityStorageConnector<T = unknown> extends IComponent {\n\t/**\n\t * Get the schema for the entities.\n\t * @returns The schema for the entities.\n\t */\n\tgetSchema(): IEntitySchema;\n\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The id of the entity.\n\t */\n\tset(entity: T, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tsetBatch(entities: T[]): Promise<void>;\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get, or the index value if secondaryIndex is set.\n\t * @param secondaryIndex Get the item using a secondary index.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tget(\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<T | undefined>;\n\n\t/**\n\t * Remove the entity.\n\t * @param id The id of the entity to remove.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns Nothing.\n\t */\n\tremove(id: string, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Remove multiple entities by id.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tremoveBatch(ids: string[]): Promise<void>;\n\n\t/**\n\t * Query all the entities which match the conditions.\n\t * @param conditions The conditions to match for the entities.\n\t * @param sortProperties The optional sort order.\n\t * @param properties The optional properties to return, defaults to all.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t */\n\tquery(\n\t\tconditions?: EntityCondition<T>,\n\t\tsortProperties?: {\n\t\t\tproperty: keyof T;\n\t\t\tsortDirection: SortDirection;\n\t\t}[],\n\t\tproperties?: (keyof T)[],\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The entities, which can be partial if a limited keys list was provided.\n\t\t */\n\t\tentities: Partial<T>[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more entities.\n\t\t */\n\t\tcursor?: string;\n\t}>;\n\n\t/**\n\t * Remove all entities from the storage.\n\t * @returns Nothing.\n\t */\n\tempty(): Promise<void>;\n\n\t/**\n\t * Count all the entities which match the conditions.\n\t * @returns The total count of entities in the storage.\n\t */\n\tcount(): Promise<number>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IEntityStorageConnector.js","sourceRoot":"","sources":["../../../src/models/IEntityStorageConnector.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IComponent } from \"@twin.org/core\";\nimport type { EntityCondition, IEntitySchema, SortDirection } from \"@twin.org/entity\";\n\n/**\n * Interface describing an entity storage connector.\n */\nexport interface IEntityStorageConnector<T = unknown> extends IComponent {\n\t/**\n\t * Get the schema for the entities.\n\t * @returns The schema for the entities.\n\t */\n\tgetSchema(): IEntitySchema;\n\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The id of the entity.\n\t */\n\tset(entity: T, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tsetBatch(entities: T[]): Promise<void>;\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get, or the index value if secondaryIndex is set.\n\t * @param secondaryIndex Get the item using a secondary index.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tget(\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<T | undefined>;\n\n\t/**\n\t * Remove the entity.\n\t * @param id The id of the entity to remove.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns Nothing.\n\t */\n\tremove(id: string, conditions?: { property: keyof T; value: unknown }[]): Promise<void>;\n\n\t/**\n\t * Remove multiple entities by id.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tremoveBatch(ids: string[]): Promise<void>;\n\n\t/**\n\t * Query all the entities which match the conditions.\n\t * @param conditions The conditions to match for the entities.\n\t * @param sortProperties The optional sort order.\n\t * @param properties The optional properties to return, defaults to all.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t */\n\tquery(\n\t\tconditions?: EntityCondition<T>,\n\t\tsortProperties?: {\n\t\t\tproperty: keyof T;\n\t\t\tsortDirection: SortDirection;\n\t\t}[],\n\t\tproperties?: (keyof T)[],\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The entities, which can be partial if a limited keys list was provided.\n\t\t */\n\t\tentities: Partial<T>[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more entities.\n\t\t */\n\t\tcursor?: string;\n\t}>;\n\n\t/**\n\t * Remove all entities from the storage.\n\t * @returns Nothing.\n\t */\n\tempty(): Promise<void>;\n\n\t/**\n\t * Count all the entities which match the conditions.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The total count of entities in the storage.\n\t */\n\tcount(conditions?: EntityCondition<T>): Promise<number>;\n}\n"]}
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageCountRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageCountRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Count the entries in entity storage.\n */\n
|
|
1
|
+
{"version":3,"file":"IEntityStorageCountRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageCountRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Count the entries in entity storage.\n */\nexport interface IEntityStorageCountRequest {\n\t/**\n\t * The query parameters.\n\t */\n\tquery?: {\n\t\t/**\n\t\t * The optional conditions to filter the count, JSON encoded EntityCondition.\n\t\t */\n\t\tconditions?: string;\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageGetRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageGetRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Get an entry from entity storage.\n */\nexport interface IEntityStorageGetRequest {\n\t/**\n\t * The parameters from the path.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the entity to get.\n\t\t */\n\t\tid: string;\n\t};\n\n\t/**\n\t * The query parameters.\n\t */\n\tquery?: {\n\t\t/**\n\t\t * The secondary index to query with the id.\n\t\t */\n\t\tsecondaryIndex?: string;\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IEntityStorageGetRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageGetRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Get an entry from entity storage.\n */\nexport interface IEntityStorageGetRequest {\n\t/**\n\t * The parameters from the path.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the entity to get.\n\t\t */\n\t\tid: string;\n\t};\n\n\t/**\n\t * The query parameters.\n\t */\n\tquery?: {\n\t\t/**\n\t\t * The secondary index to query with the id.\n\t\t */\n\t\tsecondaryIndex?: string;\n\n\t\t/**\n\t\t * The optional conditions to match for the entity, JSON encoded array of property/value pairs.\n\t\t */\n\t\tconditions?: string;\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageRemoveRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageRemoveRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Remove an entry from entity storage.\n */\nexport interface IEntityStorageRemoveRequest {\n\t/**\n\t * The parameters from the path.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the entity to remove.\n\t\t */\n\t\tid: string;\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IEntityStorageRemoveRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageRemoveRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Remove an entry from entity storage.\n */\nexport interface IEntityStorageRemoveRequest {\n\t/**\n\t * The parameters from the path.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the entity to remove.\n\t\t */\n\t\tid: string;\n\t};\n\n\t/**\n\t * The query parameters.\n\t */\n\tquery?: {\n\t\t/**\n\t\t * The optional conditions to match for the entity, JSON encoded array of property/value pairs.\n\t\t */\n\t\tconditions?: string;\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntityStorageSetRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageSetRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Set an entry in entity storage.\n */\nexport interface IEntityStorageSetRequest {\n\t/**\n\t * The data to be used in the entity.\n\t */\n\tbody: unknown;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IEntityStorageSetRequest.js","sourceRoot":"","sources":["../../../../src/models/api/IEntityStorageSetRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Set an entry in entity storage.\n */\nexport interface IEntityStorageSetRequest {\n\t/**\n\t * The data to be used in the entity.\n\t */\n\tbody: unknown;\n\n\t/**\n\t * The query parameters.\n\t */\n\tquery?: {\n\t\t/**\n\t\t * The optional conditions to match for the entity, JSON encoded array of property/value pairs.\n\t\t */\n\t\tconditions?: string;\n\t};\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type EntityCondition, type IEntitySchema } from "@twin.org/entity";
|
|
2
|
+
/**
|
|
3
|
+
* Helper class for performing schema migrations between two connectors.
|
|
4
|
+
*/
|
|
5
|
+
export declare class EntityHelper {
|
|
6
|
+
/**
|
|
7
|
+
* Runtime name for the class.
|
|
8
|
+
*/
|
|
9
|
+
static readonly CLASS_NAME: string;
|
|
10
|
+
/**
|
|
11
|
+
* Prepare the entity by handling undefined and null values and validating it against the schema.
|
|
12
|
+
* @param entity The entity to handle undefined and null values for.
|
|
13
|
+
* @param schema The schema to validate the entity against.
|
|
14
|
+
* @param additionalProperties Optional list of additional properties to set on the entity.
|
|
15
|
+
* @returns The entity with undefined and null values handled.
|
|
16
|
+
*/
|
|
17
|
+
static prepareEntity<T>(entity: T, schema: IEntitySchema<T>, additionalProperties?: {
|
|
18
|
+
property: string;
|
|
19
|
+
value: unknown;
|
|
20
|
+
}[]): T;
|
|
21
|
+
/**
|
|
22
|
+
* Un-prepare the entity by removing null values.
|
|
23
|
+
* @param entity The entity to handle undefined and null values for.
|
|
24
|
+
* @param removeProperties Optional list of properties to remove from the entity.
|
|
25
|
+
* @returns The entity with undefined and null values handled.
|
|
26
|
+
*/
|
|
27
|
+
static unPrepareEntity<T>(entity: Partial<T> | undefined, removeProperties?: string[]): T;
|
|
28
|
+
/**
|
|
29
|
+
* Deep-clone condition tree and map `undefined` to `null` on Equals/NotEquals leaves
|
|
30
|
+
* so in-memory evaluation matches stored-null semantics (optional absent props are stored as null).
|
|
31
|
+
* @param condition The user-supplied condition (not mutated).
|
|
32
|
+
* @returns A clone safe to pass to check.
|
|
33
|
+
*/
|
|
34
|
+
static normalizeConditionValues<T>(condition: EntityCondition<T>): EntityCondition<T>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type IContextIds } from "@twin.org/context";
|
|
2
|
+
import { type IEntitySchemaDiff } from "@twin.org/entity";
|
|
3
|
+
import type { IEntityStorageConnector } from "../models/IEntityStorageConnector.js";
|
|
4
|
+
import type { IEntityStorageMigrationConnector } from "../models/IEntityStorageMigrationConnector.js";
|
|
5
|
+
import type { IMigrationOptions } from "../models/IMigrationOptions.js";
|
|
6
|
+
/**
|
|
7
|
+
* Helper class for performing schema migrations between two connectors.
|
|
8
|
+
*/
|
|
9
|
+
export declare class MigrationHelper {
|
|
10
|
+
/**
|
|
11
|
+
* Runtime name for the class.
|
|
12
|
+
*/
|
|
13
|
+
static readonly CLASS_NAME: string;
|
|
14
|
+
/**
|
|
15
|
+
* Performs a migration between two connectors, using the provided options and schema diff to control the migration behaviour.
|
|
16
|
+
* @param sourceConnector The connector to migrate from to allow the migration helper to create the new connector and finalize the migration.
|
|
17
|
+
* @param targetEntitySchemaName The name of the new entity schema.
|
|
18
|
+
* @param renames An optional list of property renames to apply during migration.
|
|
19
|
+
* @param options Options controlling migration behaviour.
|
|
20
|
+
* @param loggingComponentType An optional logging component type to use for bootstrapping and starting connectors if necessary.
|
|
21
|
+
* @returns The connector for the new schema and the number of entities successfully migrated, the sourceConnector will no longer be usable, finalConnector will be undefined if no migration was necessary.
|
|
22
|
+
*/
|
|
23
|
+
static migrate<T, U>(sourceConnector: IEntityStorageMigrationConnector<T>, targetEntitySchemaName: string, renames?: {
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
}[], options?: IMigrationOptions<T, U>, loggingComponentType?: string): Promise<{
|
|
27
|
+
finalConnector?: IEntityStorageConnector<U>;
|
|
28
|
+
migrated: number;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Generic per-partition migration loop.
|
|
32
|
+
* @param source Connector to read from (current schema, already bootstrapped).
|
|
33
|
+
* @param target Connector to write to (new schema, already bootstrapped).
|
|
34
|
+
* @param partitionContextIds The context ids to use for the migration, used for partitioning and can be used in the transform function when `options.transformEntityProperty` is provided.
|
|
35
|
+
* @param schemaDiff The schema diff.
|
|
36
|
+
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
37
|
+
* @returns The number of entities successfully migrated.
|
|
38
|
+
*/
|
|
39
|
+
static migrateEntities<T = unknown, U = T>(source: IEntityStorageMigrationConnector<T>, target: IEntityStorageConnector<U>, partitionContextIds: IContextIds[], schemaDiff: IEntitySchemaDiff<T, U>, options?: IMigrationOptions<T, U>): Promise<number>;
|
|
40
|
+
/**
|
|
41
|
+
* Generic per-partition migration loop.
|
|
42
|
+
* @param source Connector to read from (current schema, already bootstrapped).
|
|
43
|
+
* @param target Connector to write to (new schema, already bootstrapped).
|
|
44
|
+
* @param partitionTotal The total number of partitions to migrate, used for progress reporting.
|
|
45
|
+
* @param partitionIndex The index of the current partition being migrated, used for progress reporting.
|
|
46
|
+
* @param schemaDiff Schema diff used to add nullable defaults and drop removed fields when `options.transformEntity` is not provided.
|
|
47
|
+
* @param options Optional migration controls (batchSize, transformEntity, onProgress).
|
|
48
|
+
* @returns The number of entities successfully migrated.
|
|
49
|
+
*/
|
|
50
|
+
static migratePartition<T = unknown, U = unknown>(source: IEntityStorageConnector<T>, target: IEntityStorageConnector<U>, partitionTotal: number, partitionIndex: number, schemaDiff: IEntitySchemaDiff<T, U>, options?: IMigrationOptions<T, U>): Promise<number>;
|
|
51
|
+
/**
|
|
52
|
+
* Applies the entity transformation for migration, using the provided options and schema diff.
|
|
53
|
+
* @param entity The entity to transform.
|
|
54
|
+
* @param schemaDiff The schema diff between the old and new schemas.
|
|
55
|
+
* @param options The migration options.
|
|
56
|
+
* @returns The transformed entity ready to be written to the new schema.
|
|
57
|
+
* @throws GeneralError if a transformation is required for an object or array property but no `options.transformEntityProperty` function is provided.
|
|
58
|
+
* @throws GeneralError if coercion of a modified property results in undefined for a non-optional target property.
|
|
59
|
+
*/
|
|
60
|
+
static applyEntityTransform<T = unknown, U = unknown>(entity: Partial<T>, schemaDiff: IEntitySchemaDiff<T, U>, options?: IMigrationOptions<T, U>): U;
|
|
61
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./factories/entityStorageConnectorFactory.js";
|
|
2
|
+
export * from "./helpers/entityHelper.js";
|
|
3
|
+
export * from "./helpers/migrationHelper.js";
|
|
2
4
|
export * from "./models/api/IEntityStorageCountRequest.js";
|
|
3
|
-
export * from "./models/api/IEntityStorageEmptyRequest.js";
|
|
4
5
|
export * from "./models/api/IEntityStorageCountResponse.js";
|
|
6
|
+
export * from "./models/api/IEntityStorageEmptyRequest.js";
|
|
5
7
|
export * from "./models/api/IEntityStorageGetRequest.js";
|
|
6
8
|
export * from "./models/api/IEntityStorageGetResponse.js";
|
|
7
9
|
export * from "./models/api/IEntityStorageListRequest.js";
|
|
@@ -12,3 +14,5 @@ export * from "./models/api/IEntityStorageSetBatchRequest.js";
|
|
|
12
14
|
export * from "./models/api/IEntityStorageSetRequest.js";
|
|
13
15
|
export * from "./models/IEntityStorageComponent.js";
|
|
14
16
|
export * from "./models/IEntityStorageConnector.js";
|
|
17
|
+
export * from "./models/IEntityStorageMigrationConnector.js";
|
|
18
|
+
export * from "./models/IMigrationOptions.js";
|