@itwin/imodel-transformer 1.0.0-dev.9 → 1.0.0
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/CHANGELOG.md +29 -1
- package/README.md +17 -0
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +2 -0
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +9 -9
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +18 -14
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +26 -3
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +58 -20
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +96 -138
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +230 -280
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/{transformer.d.ts → imodel-transformer.d.ts} +1 -1
- package/lib/cjs/imodel-transformer.d.ts.map +1 -0
- package/lib/cjs/{transformer.js → imodel-transformer.js} +11 -2
- package/lib/cjs/imodel-transformer.js.map +1 -0
- package/package.json +6 -5
- package/lib/cjs/PendingReferenceMap.d.ts +0 -37
- package/lib/cjs/PendingReferenceMap.d.ts.map +0 -1
- package/lib/cjs/PendingReferenceMap.js +0 -92
- package/lib/cjs/PendingReferenceMap.js.map +0 -1
- package/lib/cjs/transformer.d.ts.map +0 -1
- package/lib/cjs/transformer.js.map +0 -1
|
@@ -19,8 +19,6 @@ const core_common_1 = require("@itwin/core-common");
|
|
|
19
19
|
const IModelExporter_1 = require("./IModelExporter");
|
|
20
20
|
const IModelImporter_1 = require("./IModelImporter");
|
|
21
21
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
22
|
-
const PendingReferenceMap_1 = require("./PendingReferenceMap");
|
|
23
|
-
const EntityMap_1 = require("./EntityMap");
|
|
24
22
|
const IModelCloneContext_1 = require("./IModelCloneContext");
|
|
25
23
|
const EntityUnifier_1 = require("./EntityUnifier");
|
|
26
24
|
const Algo_1 = require("./Algo");
|
|
@@ -31,30 +29,6 @@ const nullLastProvenanceEntityInfo = {
|
|
|
31
29
|
aspectVersion: "",
|
|
32
30
|
aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
|
|
33
31
|
};
|
|
34
|
-
/**
|
|
35
|
-
* A container for tracking the state of a partially committed entity and finalizing it when it's ready to be fully committed
|
|
36
|
-
* @internal
|
|
37
|
-
*/
|
|
38
|
-
class PartiallyCommittedEntity {
|
|
39
|
-
constructor(
|
|
40
|
-
/**
|
|
41
|
-
* A set of "model|element ++ ID64" pairs, (e.g. `model0x11` or `element0x12`)
|
|
42
|
-
* It is possible for the submodel of an element to be separately resolved from the actual element,
|
|
43
|
-
* so its resolution must be tracked separately
|
|
44
|
-
*/
|
|
45
|
-
_missingReferences, _onComplete) {
|
|
46
|
-
this._missingReferences = _missingReferences;
|
|
47
|
-
this._onComplete = _onComplete;
|
|
48
|
-
}
|
|
49
|
-
resolveReference(id) {
|
|
50
|
-
this._missingReferences.delete(id);
|
|
51
|
-
if (this._missingReferences.size === 0)
|
|
52
|
-
this._onComplete();
|
|
53
|
-
}
|
|
54
|
-
forceComplete() {
|
|
55
|
-
this._onComplete();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
32
|
/**
|
|
59
33
|
* Apply a function to each Id64 in a supported container type of Id64s.
|
|
60
34
|
* Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
|
|
@@ -172,7 +146,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
172
146
|
if (this._isProvenanceInitTransform) {
|
|
173
147
|
return "forward";
|
|
174
148
|
}
|
|
175
|
-
if (!this.
|
|
149
|
+
if (!this._options.argsForProcessChanges) {
|
|
176
150
|
return "not-sync";
|
|
177
151
|
}
|
|
178
152
|
try {
|
|
@@ -217,14 +191,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
217
191
|
*/
|
|
218
192
|
constructor(source, target, options) {
|
|
219
193
|
super();
|
|
220
|
-
/** map of (unprocessed element, referencing processed element) pairs to the partially committed element that needs the reference resolved
|
|
221
|
-
* and have some helper methods below for now */
|
|
222
|
-
this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
|
|
223
194
|
/** a set of elements for which source provenance will be explicitly tracked by ExternalSourceAspects */
|
|
224
195
|
this._elementsWithExplicitlyTrackedProvenance = new Set();
|
|
225
|
-
|
|
226
|
-
this.
|
|
227
|
-
this._isSynchronization = false;
|
|
196
|
+
this._partiallyCommittedElementIds = new Set();
|
|
197
|
+
this._partiallyCommittedAspectIds = new Set();
|
|
228
198
|
/**
|
|
229
199
|
* A private variable meant to be set by tests which have an outdated way of setting up transforms. In all synchronizations today we expect to find an ESA in the branch db which describes the master -> branch relationship.
|
|
230
200
|
* The exception to this is the first transform aka the provenance initializing transform which requires that the master imodel and the branch imodel are identical at the time of provenance initialization.
|
|
@@ -233,10 +203,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
233
203
|
*/
|
|
234
204
|
this._allowNoScopingESA = false;
|
|
235
205
|
this._changesetRanges = undefined;
|
|
236
|
-
/** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
|
|
237
|
-
* @note Currently only tracks elements which were not exported.
|
|
238
|
-
*/
|
|
239
|
-
this._skippedEntities = new Set();
|
|
240
206
|
/**
|
|
241
207
|
* Previously the transformer would insert provenance always pointing to the "target" relationship.
|
|
242
208
|
* It should (and now by default does) instead insert provenance pointing to the provenanceSource
|
|
@@ -276,7 +242,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
276
242
|
// eslint-disable-next-line deprecation/deprecation
|
|
277
243
|
danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
|
|
278
244
|
branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
|
|
245
|
+
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
279
246
|
};
|
|
247
|
+
// check if authorization client is defined
|
|
248
|
+
if (core_backend_1.IModelHost.authorizationClient === undefined) {
|
|
249
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Authorization client is not set in IModelHost. If the transformer needs an accessToken, then it will fail.");
|
|
250
|
+
}
|
|
280
251
|
this._isProvenanceInitTransform = this._options
|
|
281
252
|
.wasSourceIModelCopiedToTarget
|
|
282
253
|
? true
|
|
@@ -360,9 +331,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
360
331
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
361
332
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
362
333
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
363
|
-
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
364
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
365
|
-
`this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
|
|
366
334
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
367
335
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
368
336
|
}
|
|
@@ -605,8 +573,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
605
573
|
let madeChange = false;
|
|
606
574
|
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
607
575
|
return madeChange;
|
|
608
|
-
const fallbackSyncVersionToUse = this._options.unsafeFallbackSyncVersion ?? "";
|
|
609
|
-
const fallbackReverseSyncVersionToUse = this._options.unsafeFallbackReverseSyncVersion ??
|
|
576
|
+
const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
|
|
577
|
+
const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
|
|
578
|
+
"";
|
|
610
579
|
if (aspectProps.version === undefined ||
|
|
611
580
|
(aspectProps.version === "" &&
|
|
612
581
|
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
@@ -744,7 +713,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
744
713
|
targetScopeElementId: this.targetScopeElementId,
|
|
745
714
|
isReverseSynchronization: this.isReverseSynchronization,
|
|
746
715
|
fn,
|
|
747
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ??
|
|
716
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
|
|
748
717
|
});
|
|
749
718
|
}
|
|
750
719
|
/**
|
|
@@ -866,7 +835,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
866
835
|
}
|
|
867
836
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
868
837
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
869
|
-
* @note Not relevant for
|
|
838
|
+
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
870
839
|
*/
|
|
871
840
|
shouldDetectDeletes() {
|
|
872
841
|
nodeAssert(this._syncType !== undefined);
|
|
@@ -876,9 +845,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
876
845
|
* Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
|
|
877
846
|
* in the source iModel.
|
|
878
847
|
* @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
|
|
879
|
-
* This method is only called during [[
|
|
848
|
+
* This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
|
|
880
849
|
* [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
881
|
-
* necessary when
|
|
850
|
+
* necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
|
|
882
851
|
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
883
852
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
884
853
|
*/
|
|
@@ -908,12 +877,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
908
877
|
}
|
|
909
878
|
});
|
|
910
879
|
}
|
|
911
|
-
/**
|
|
912
|
-
* @deprecated in 3.x, this no longer has any effect except emitting a warning
|
|
913
|
-
*/
|
|
914
|
-
skipElement(_sourceElement) {
|
|
915
|
-
core_bentley_1.Logger.logWarning(loggerCategory, "Tried to defer/skip an element, which is no longer necessary");
|
|
916
|
-
}
|
|
917
880
|
/** Transform the specified sourceElement into ElementProps for the target iModel.
|
|
918
881
|
* @param sourceElement The Element from the source iModel to transform.
|
|
919
882
|
* @returns ElementProps for the target iModel.
|
|
@@ -958,85 +921,74 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
958
921
|
nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
|
|
959
922
|
return this._hasElementChangedCache.has(sourceElement.id);
|
|
960
923
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
(0, core_bentley_1.assert)(false, `unreachable; entity was '${entity.constructor.name}' not an Element, Relationship, or ElementAspect`);
|
|
972
|
-
}
|
|
973
|
-
/** callback to perform when a partial element says it's ready to be completed
|
|
974
|
-
* transforms the source element with all references now valid, then updates the partial element with the results
|
|
975
|
-
*/
|
|
976
|
-
makePartialEntityCompleter(sourceEntity) {
|
|
977
|
-
return () => {
|
|
978
|
-
const targetId = this.context.findTargetEntityId(core_backend_1.EntityReferences.from(sourceEntity));
|
|
979
|
-
if (!core_backend_1.EntityReferences.isValid(targetId))
|
|
980
|
-
throw Error(`${sourceEntity.id} has not been inserted into the target yet, the completer is invalid. This is a bug.`);
|
|
981
|
-
const onEntityTransform = IModelTransformer.transformCallbackFor(this, sourceEntity);
|
|
982
|
-
const updateEntity = EntityUnifier_1.EntityUnifier.updaterFor(this.targetDb, sourceEntity);
|
|
983
|
-
const targetProps = onEntityTransform.call(this, sourceEntity);
|
|
984
|
-
if (sourceEntity instanceof core_backend_1.Relationship) {
|
|
985
|
-
targetProps.sourceId =
|
|
986
|
-
this.context.findTargetElementId(sourceEntity.sourceId);
|
|
987
|
-
targetProps.targetId =
|
|
988
|
-
this.context.findTargetElementId(sourceEntity.targetId);
|
|
924
|
+
completePartiallyCommittedElements() {
|
|
925
|
+
for (const sourceElementId of this._partiallyCommittedElementIds) {
|
|
926
|
+
const sourceElement = this.sourceDb.elements.getElement({
|
|
927
|
+
id: sourceElementId,
|
|
928
|
+
wantGeometry: this.exporter.wantGeometry,
|
|
929
|
+
wantBRepData: this.exporter.wantGeometry,
|
|
930
|
+
});
|
|
931
|
+
const targetId = this.context.findTargetElementId(sourceElementId);
|
|
932
|
+
if (core_bentley_1.Id64.isInvalid(targetId)) {
|
|
933
|
+
throw new Error(`source-target element mapping not found for element "${sourceElementId}" when completing partially committed elements. This is a bug.`);
|
|
989
934
|
}
|
|
990
|
-
|
|
991
|
-
this.
|
|
992
|
-
}
|
|
935
|
+
const targetProps = this.onTransformElement(sourceElement);
|
|
936
|
+
this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
|
|
937
|
+
}
|
|
993
938
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
this._skippedEntities.has(referenceId);
|
|
1006
|
-
if (alreadyProcessed)
|
|
1007
|
-
continue;
|
|
1008
|
-
core_bentley_1.Logger.logTrace(loggerCategory, `Deferring resolution of reference '${referenceId}' of element '${entity.id}'`);
|
|
1009
|
-
const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
1010
|
-
entityReference: referenceId,
|
|
939
|
+
completePartiallyCommittedAspects() {
|
|
940
|
+
for (const sourceAspectId of this._partiallyCommittedAspectIds) {
|
|
941
|
+
const sourceAspect = this.sourceDb.elements.getAspect(sourceAspectId);
|
|
942
|
+
const targetAspectId = this.context.findTargetAspectId(sourceAspectId);
|
|
943
|
+
if (core_bentley_1.Id64.isInvalid(targetAspectId)) {
|
|
944
|
+
throw new Error(`source-target aspect mapping not found for aspect "${sourceAspectId}" when completing partially committed aspects. This is a bug.`);
|
|
945
|
+
}
|
|
946
|
+
const targetAspectProps = this.onTransformElementAspect(sourceAspect);
|
|
947
|
+
this.targetDb.elements.updateAspect({
|
|
948
|
+
...targetAspectProps,
|
|
949
|
+
id: targetAspectId,
|
|
1011
950
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
doAllReferencesExistInTarget(entity) {
|
|
954
|
+
let allReferencesExist = true;
|
|
955
|
+
for (const referenceId of entity.getReferenceIds()) {
|
|
956
|
+
const referencedEntityId = core_backend_1.EntityReferences.toId64(referenceId);
|
|
957
|
+
if (referencedEntityId === core_common_1.IModel.repositoryModelId ||
|
|
958
|
+
referencedEntityId === core_common_1.IModel.dictionaryId ||
|
|
959
|
+
referencedEntityId === "0xe") {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (allReferencesExist &&
|
|
963
|
+
!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
|
|
964
|
+
// if we care about references existing then we cannot return early and must check all other references.
|
|
965
|
+
if (this._options.danglingReferencesBehavior === "ignore") {
|
|
966
|
+
return false;
|
|
1025
967
|
}
|
|
968
|
+
allReferencesExist = false;
|
|
1026
969
|
}
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
if (!this._partiallyCommittedEntities.has(entity))
|
|
1030
|
-
this._partiallyCommittedEntities.set(entity, thisPartialElem);
|
|
970
|
+
if (this._options.danglingReferencesBehavior === "reject") {
|
|
971
|
+
this.assertReferenceExistsInSource(referenceId, entity);
|
|
1031
972
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
973
|
+
}
|
|
974
|
+
return allReferencesExist;
|
|
975
|
+
}
|
|
976
|
+
assertReferenceExistsInSource(referenceId, entity) {
|
|
977
|
+
const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
|
|
978
|
+
entityReference: referenceId,
|
|
979
|
+
});
|
|
980
|
+
if (!referencedExistsInSource) {
|
|
981
|
+
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
|
|
982
|
+
`Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
|
|
983
|
+
"This must have been caused by an upstream application that changed the iModel.",
|
|
984
|
+
"You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this,",
|
|
985
|
+
`and the referenceId found on "${entity.id}" will not be carried over to corresponding target element.`,
|
|
986
|
+
].join("\n"));
|
|
1035
987
|
}
|
|
1036
988
|
}
|
|
1037
989
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
1038
990
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
1039
|
-
* @note This method is called from [[
|
|
991
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1040
992
|
*/
|
|
1041
993
|
async processElement(sourceElementId) {
|
|
1042
994
|
await this.initialize();
|
|
@@ -1047,7 +999,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1047
999
|
}
|
|
1048
1000
|
/** Import child elements into the target IModelDb
|
|
1049
1001
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
1050
|
-
* @note This method is called from [[
|
|
1002
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1051
1003
|
*/
|
|
1052
1004
|
async processChildElements(sourceElementId) {
|
|
1053
1005
|
await this.initialize();
|
|
@@ -1059,24 +1011,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1059
1011
|
shouldExportElement(_sourceElement) {
|
|
1060
1012
|
return true;
|
|
1061
1013
|
}
|
|
1062
|
-
onSkipElement(sourceElementId) {
|
|
1063
|
-
if (this.context.findTargetElementId(sourceElementId) !== core_bentley_1.Id64.invalid) {
|
|
1064
|
-
// element already has provenance
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
core_bentley_1.Logger.logInfo(loggerCategory, `Element '${sourceElementId}' won't be exported. Marking its references as resolved`);
|
|
1068
|
-
const elementKey = `e${sourceElementId}`;
|
|
1069
|
-
this._skippedEntities.add(elementKey);
|
|
1070
|
-
// Mark any existing pending references to the skipped element as resolved.
|
|
1071
|
-
for (const referencer of this._pendingReferences.getReferencersByEntityKey(elementKey)) {
|
|
1072
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, elementKey);
|
|
1073
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1074
|
-
if (!pendingRef)
|
|
1075
|
-
continue;
|
|
1076
|
-
pendingRef.resolveReference(elementKey);
|
|
1077
|
-
this._pendingReferences.delete(key);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
1014
|
/**
|
|
1081
1015
|
* If they haven't been already, import all of the required references
|
|
1082
1016
|
* @internal do not call, override or implement this, it will be removed
|
|
@@ -1143,11 +1077,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1143
1077
|
onExportElement(sourceElement) {
|
|
1144
1078
|
let targetElementId;
|
|
1145
1079
|
let targetElementProps;
|
|
1146
|
-
if (this._options.
|
|
1147
|
-
targetElementId = sourceElement.id;
|
|
1148
|
-
targetElementProps = this.onTransformElement(sourceElement);
|
|
1149
|
-
}
|
|
1150
|
-
else if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1080
|
+
if (this._options.wasSourceIModelCopiedToTarget) {
|
|
1151
1081
|
targetElementId = sourceElement.id;
|
|
1152
1082
|
targetElementProps =
|
|
1153
1083
|
this.targetDb.elements.getElementProps(targetElementId);
|
|
@@ -1186,17 +1116,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1186
1116
|
}
|
|
1187
1117
|
if (!this.hasElementChanged(sourceElement))
|
|
1188
1118
|
return;
|
|
1189
|
-
this.
|
|
1119
|
+
if (!this.doAllReferencesExistInTarget(sourceElement)) {
|
|
1120
|
+
this._partiallyCommittedElementIds.add(sourceElement.id);
|
|
1121
|
+
}
|
|
1190
1122
|
// targetElementId will be valid (indicating update) or undefined (indicating insert)
|
|
1191
1123
|
targetElementProps.id = core_bentley_1.Id64.isValid(targetElementId)
|
|
1192
1124
|
? targetElementId
|
|
1193
1125
|
: undefined;
|
|
1126
|
+
if (this._options.preserveElementIdsForFiltering) {
|
|
1127
|
+
const isValid = core_bentley_1.Id64.isValid(targetElementId);
|
|
1128
|
+
if (isValid && targetElementId !== sourceElement.id) {
|
|
1129
|
+
// Element found with different id
|
|
1130
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`);
|
|
1131
|
+
}
|
|
1132
|
+
else if (isValid && targetElementId === sourceElement.id) {
|
|
1133
|
+
// targetElementId is valid (indicating update)
|
|
1134
|
+
this.importer.markElementToUpdateDuringPreserveIds(sourceElement.id);
|
|
1135
|
+
}
|
|
1136
|
+
else if (!isValid) {
|
|
1137
|
+
const sourceInTargetElemProps = this.targetDb.elements.tryGetElementProps(sourceElement.id);
|
|
1138
|
+
// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
|
|
1139
|
+
if (sourceInTargetElemProps) {
|
|
1140
|
+
// Element id is already taken by another element
|
|
1141
|
+
throw new Error(`Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
// Element id in target is available to be remapped
|
|
1145
|
+
targetElementProps.id = sourceElement.id;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1194
1149
|
if (!this._options.wasSourceIModelCopiedToTarget) {
|
|
1195
1150
|
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
|
|
1196
1151
|
}
|
|
1197
1152
|
this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
|
|
1198
|
-
// now that we've mapped this elem we can fix unmapped references to it
|
|
1199
|
-
this.resolvePendingReferences(sourceElement);
|
|
1200
1153
|
// the transformer does not currently 'split' or 'join' any elements, therefore, it does not
|
|
1201
1154
|
// insert external source aspects because federation guids are sufficient for this.
|
|
1202
1155
|
// Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
|
|
@@ -1224,16 +1177,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1224
1177
|
this.markLastProvenance(provenance, { isRelationship: false });
|
|
1225
1178
|
}
|
|
1226
1179
|
}
|
|
1227
|
-
resolvePendingReferences(entity) {
|
|
1228
|
-
for (const referencer of this._pendingReferences.getReferencers(entity)) {
|
|
1229
|
-
const key = PendingReferenceMap_1.PendingReference.from(referencer, entity);
|
|
1230
|
-
const pendingRef = this._pendingReferences.get(key);
|
|
1231
|
-
if (!pendingRef)
|
|
1232
|
-
continue;
|
|
1233
|
-
pendingRef.resolveReference(core_backend_1.EntityReferences.from(entity));
|
|
1234
|
-
this._pendingReferences.delete(key);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
1180
|
/** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
|
|
1238
1181
|
* This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
|
|
1239
1182
|
*/
|
|
@@ -1258,7 +1201,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1258
1201
|
return;
|
|
1259
1202
|
const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
|
|
1260
1203
|
this.importer.importModel(targetModelProps);
|
|
1261
|
-
this.resolvePendingReferences(sourceModel);
|
|
1262
1204
|
}
|
|
1263
1205
|
/** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
|
|
1264
1206
|
onDeleteModel(sourceModelId) {
|
|
@@ -1332,7 +1274,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1332
1274
|
}
|
|
1333
1275
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1334
1276
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1335
|
-
* @note This method is called from [[
|
|
1277
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1336
1278
|
*/
|
|
1337
1279
|
async processModel(sourceModeledElementId) {
|
|
1338
1280
|
await this.initialize();
|
|
@@ -1342,7 +1284,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1342
1284
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1343
1285
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1344
1286
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1345
|
-
* @note This method is called from [[
|
|
1287
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1346
1288
|
*/
|
|
1347
1289
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1348
1290
|
await this.initialize();
|
|
@@ -1399,46 +1341,50 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1399
1341
|
targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
|
|
1400
1342
|
return targetModelProps;
|
|
1401
1343
|
}
|
|
1402
|
-
/**
|
|
1403
|
-
*
|
|
1404
|
-
*/
|
|
1405
|
-
async processDeferredElements(_numRetries = 3) { }
|
|
1406
|
-
/** called at the end of a transformation,
|
|
1344
|
+
/**
|
|
1345
|
+
* Called at the end of a transformation,
|
|
1407
1346
|
* updates the target scope element to say that transformation up through the
|
|
1408
1347
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1409
1348
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1410
1349
|
*
|
|
1411
|
-
* You generally should not call this function yourself and use [[
|
|
1350
|
+
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1412
1351
|
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1413
|
-
* @note
|
|
1414
|
-
*
|
|
1352
|
+
* @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
|
|
1353
|
+
* unless the `initializeReverseSyncVersion` option is set to `true`
|
|
1354
|
+
*
|
|
1355
|
+
* The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
|
|
1356
|
+
* When set to `true`, it saves the reverse sync version as the current changeset of the targetDb. This is typically used for the first transformation between a master and branch iModel.
|
|
1357
|
+
* Setting `initializeReverseSyncVersion` to `true` has the effect of making it so any changesets in the branch iModel at the time of the first transformation will be ignored during any future reverse synchronizations from the branch to the master iModel.
|
|
1358
|
+
*
|
|
1359
|
+
* Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
|
|
1360
|
+
* Setting initializeReverseSyncVersion to true during a forward transformation could overwrite this correct reverseSyncVersion and should only be done during the first transformation between a master and branch iModel.
|
|
1415
1361
|
*/
|
|
1416
|
-
updateSynchronizationVersion({
|
|
1417
|
-
const
|
|
1418
|
-
this._sourceChangeDataState !== "has-changes"
|
|
1419
|
-
|
|
1420
|
-
if (notForcedAndHasNoChangesAndIsntProvenanceInit)
|
|
1362
|
+
updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
|
|
1363
|
+
const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
|
|
1364
|
+
this._sourceChangeDataState !== "has-changes";
|
|
1365
|
+
if (shouldSkipSyncVersionUpdate)
|
|
1421
1366
|
return;
|
|
1422
1367
|
nodeAssert(this._targetScopeProvenanceProps);
|
|
1423
1368
|
const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
|
|
1424
1369
|
const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
|
|
1425
|
-
if (this.
|
|
1426
|
-
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1427
|
-
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1428
|
-
targetVersion;
|
|
1429
|
-
}
|
|
1430
|
-
else if (this.isReverseSynchronization) {
|
|
1370
|
+
if (this.isReverseSynchronization) {
|
|
1431
1371
|
const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
|
|
1432
1372
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
|
|
1433
1373
|
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1434
1374
|
sourceVersion;
|
|
1435
1375
|
}
|
|
1436
|
-
else
|
|
1376
|
+
else {
|
|
1437
1377
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1438
1378
|
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1379
|
+
// save reverse sync version
|
|
1380
|
+
if (initializeReverseSyncVersion) {
|
|
1381
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
|
|
1382
|
+
this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
|
|
1383
|
+
targetVersion;
|
|
1384
|
+
}
|
|
1439
1385
|
}
|
|
1440
|
-
if (this.
|
|
1441
|
-
(this._startingChangesetIndices &&
|
|
1386
|
+
if (this._options.argsForProcessChanges ||
|
|
1387
|
+
(this._startingChangesetIndices && initializeReverseSyncVersion)) {
|
|
1442
1388
|
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1443
1389
|
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
1444
1390
|
const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
|
|
@@ -1446,16 +1392,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1446
1392
|
core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
|
|
1447
1393
|
const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
|
|
1448
1394
|
const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1395
|
+
// Determine which keys to clear and update based on the synchronization direction
|
|
1396
|
+
let syncChangesetsToClearKey;
|
|
1397
|
+
let syncChangesetsToUpdateKey;
|
|
1398
|
+
if (this.isReverseSynchronization) {
|
|
1399
|
+
syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
|
|
1400
|
+
syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
|
|
1404
|
+
syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
|
|
1405
|
+
}
|
|
1406
|
+
// NOTE that as documented in [[processChanges]], this assumes that right after
|
|
1407
|
+
// transformation finalization, the work will be saved immediately, otherwise we've
|
|
1408
|
+
// just marked this changeset as a synchronization to ignore, and the user can add other
|
|
1409
|
+
// stuff to it which would break future synchronizations
|
|
1459
1410
|
for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
|
|
1460
1411
|
jsonProps[syncChangesetsToUpdateKey].push(i);
|
|
1461
1412
|
// Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
|
|
@@ -1478,23 +1429,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1478
1429
|
this.clearCachedSynchronizationVersion();
|
|
1479
1430
|
}
|
|
1480
1431
|
// FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
|
|
1481
|
-
|
|
1432
|
+
finalizeTransformation() {
|
|
1482
1433
|
this.importer.finalize();
|
|
1483
|
-
this.updateSynchronizationVersion(
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
"The following elements were never fully resolved:",
|
|
1487
|
-
[...this._partiallyCommittedEntities.keys()].join(","),
|
|
1488
|
-
"This indicates that either some references were excluded from the transformation",
|
|
1489
|
-
"or the source has dangling references.",
|
|
1490
|
-
].join("\n");
|
|
1491
|
-
if (this._options.danglingReferencesBehavior === "reject")
|
|
1492
|
-
throw new Error(message);
|
|
1493
|
-
core_bentley_1.Logger.logWarning(loggerCategory, message);
|
|
1494
|
-
for (const partiallyCommittedElem of this._partiallyCommittedEntities.values()) {
|
|
1495
|
-
partiallyCommittedElem.forceComplete();
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1434
|
+
this.updateSynchronizationVersion({
|
|
1435
|
+
initializeReverseSyncVersion: this._isProvenanceInitTransform,
|
|
1436
|
+
});
|
|
1498
1437
|
// TODO: ignore if we remove change cache usage
|
|
1499
1438
|
if (!this._options.noDetachChangeCache) {
|
|
1500
1439
|
if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
|
|
@@ -1507,35 +1446,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1507
1446
|
this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
|
|
1508
1447
|
}
|
|
1509
1448
|
/* eslint-enable @itwin/no-internal */
|
|
1510
|
-
const defaultSaveTargetChanges = () => this.targetDb.saveChanges();
|
|
1511
|
-
await (options?.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
1512
|
-
if (this.isReverseSynchronization)
|
|
1513
|
-
this.sourceDb.saveChanges();
|
|
1514
|
-
const description = `${this._isProvenanceInitTransform
|
|
1515
|
-
? options?.provenanceInitTransformChangesetDescription ??
|
|
1516
|
-
`initialized branch provenance with master iModel: ${this.sourceDb.iModelId}`
|
|
1517
|
-
: this.isForwardSynchronization
|
|
1518
|
-
? options?.forwardSyncBranchChangesetDescription ??
|
|
1519
|
-
`Forward sync of iModel: ${this.sourceDb.iModelId}`
|
|
1520
|
-
: options?.reverseSyncMasterChangesetDescription ??
|
|
1521
|
-
`Reverse sync of iModel: ${this.sourceDb.iModelId}`}`;
|
|
1522
|
-
if (this.targetDb.isBriefcaseDb()) {
|
|
1523
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1524
|
-
await this.targetDb.pushChanges({
|
|
1525
|
-
description,
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
if (this.isReverseSynchronization && this.sourceDb.isBriefcaseDb()) {
|
|
1529
|
-
// This relies on authorizationClient on iModelHost being defined, otherwise this will fail
|
|
1530
|
-
await this.sourceDb.pushChanges({
|
|
1531
|
-
description: options?.reverseSyncBranchChangesetDescription ??
|
|
1532
|
-
`Update provenance in response to a reverse sync to iModel: ${this.targetDb.iModelId}`,
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
1449
|
}
|
|
1536
1450
|
/** Imports all relationships that subclass from the specified base class.
|
|
1537
1451
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1538
|
-
* @note This method is called from [[
|
|
1452
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1539
1453
|
*/
|
|
1540
1454
|
async processRelationships(baseRelClassFullName) {
|
|
1541
1455
|
await this.initialize();
|
|
@@ -1608,8 +1522,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1608
1522
|
}
|
|
1609
1523
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1610
1524
|
* @deprecated in 1.x. Don't use this anymore
|
|
1611
|
-
* @see
|
|
1612
|
-
* @note This method is called from [[
|
|
1525
|
+
* @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
|
|
1526
|
+
* @note This method is called from [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are undefined, so it only needs to be called directly when processing a subset of an iModel.
|
|
1613
1527
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
1614
1528
|
*/
|
|
1615
1529
|
async detectRelationshipDeletes() {
|
|
@@ -1676,22 +1590,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1676
1590
|
* This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
|
|
1677
1591
|
*/
|
|
1678
1592
|
onExportElementUniqueAspect(sourceAspect) {
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1593
|
+
const targetAspectProps = this.onTransformElementAspect(sourceAspect);
|
|
1594
|
+
if (!this.doAllReferencesExistInTarget(sourceAspect)) {
|
|
1595
|
+
this._partiallyCommittedAspectIds.add(sourceAspect.id);
|
|
1596
|
+
}
|
|
1682
1597
|
const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
|
|
1683
1598
|
this.context.remapElementAspect(sourceAspect.id, targetId);
|
|
1684
|
-
this.resolvePendingReferences(sourceAspect);
|
|
1685
1599
|
}
|
|
1686
1600
|
/** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
|
|
1687
1601
|
* This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
|
|
1688
1602
|
* @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
|
|
1689
1603
|
*/
|
|
1690
1604
|
onExportElementMultiAspects(sourceAspects) {
|
|
1691
|
-
const targetElementId = this.context.findTargetElementId(sourceAspects[0].element.id);
|
|
1692
1605
|
// Transform source ElementMultiAspects into target ElementAspectProps
|
|
1693
|
-
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA
|
|
1694
|
-
sourceAspects.forEach((a) =>
|
|
1606
|
+
const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA));
|
|
1607
|
+
sourceAspects.forEach((a) => {
|
|
1608
|
+
if (!this.doAllReferencesExistInTarget(a)) {
|
|
1609
|
+
this._partiallyCommittedAspectIds.add(a.id);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1695
1612
|
// const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
|
|
1696
1613
|
const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
|
|
1697
1614
|
const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
|
|
@@ -1701,16 +1618,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1701
1618
|
});
|
|
1702
1619
|
for (let i = 0; i < targetIds.length; ++i) {
|
|
1703
1620
|
this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
|
|
1704
|
-
this.resolvePendingReferences(sourceAspects[i]);
|
|
1705
1621
|
}
|
|
1706
1622
|
}
|
|
1707
1623
|
/** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
|
|
1708
1624
|
* @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
|
|
1709
|
-
* @param _targetElementId The ElementId of the target Element that will own the ElementAspects after transformation.
|
|
1710
1625
|
* @returns ElementAspectProps for the target iModel.
|
|
1711
1626
|
* @note A subclass can override this method to provide custom transform behavior.
|
|
1712
1627
|
*/
|
|
1713
|
-
onTransformElementAspect(sourceElementAspect
|
|
1628
|
+
onTransformElementAspect(sourceElementAspect) {
|
|
1714
1629
|
const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
|
|
1715
1630
|
return targetElementAspectProps;
|
|
1716
1631
|
}
|
|
@@ -1750,6 +1665,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1750
1665
|
nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
|
|
1751
1666
|
this._longNamedSchemasMap.set(schema.name, schemaFileName);
|
|
1752
1667
|
}
|
|
1668
|
+
/* eslint-disable-next-line deprecation/deprecation */
|
|
1753
1669
|
this.sourceDb.nativeDb.exportSchema(schema.name, this._schemaExportDir, schemaFileName);
|
|
1754
1670
|
return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
|
|
1755
1671
|
}
|
|
@@ -1790,7 +1706,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1790
1706
|
}
|
|
1791
1707
|
}
|
|
1792
1708
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1793
|
-
* @note This method is called from [[
|
|
1709
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1794
1710
|
*/
|
|
1795
1711
|
async processFonts() {
|
|
1796
1712
|
// we do not need to initialize for this since no entities are exported
|
|
@@ -1802,14 +1718,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1802
1718
|
this.context.importFont(font.id);
|
|
1803
1719
|
}
|
|
1804
1720
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1805
|
-
* @note This method is called from [[
|
|
1721
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1806
1722
|
*/
|
|
1807
1723
|
async processCodeSpecs() {
|
|
1808
1724
|
await this.initialize();
|
|
1809
1725
|
return this.exporter.exportCodeSpecs();
|
|
1810
1726
|
}
|
|
1811
1727
|
/** Cause a single CodeSpec to be exported from the source iModel and imported into the target iModel.
|
|
1812
|
-
* @note This method is called from [[
|
|
1728
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1813
1729
|
*/
|
|
1814
1730
|
async processCodeSpec(codeSpecName) {
|
|
1815
1731
|
await this.initialize();
|
|
@@ -1833,21 +1749,23 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1833
1749
|
this.context.remapElement(sourceSubjectId, targetSubjectId);
|
|
1834
1750
|
await this.processChildElements(sourceSubjectId);
|
|
1835
1751
|
await this.processSubjectSubModels(sourceSubjectId);
|
|
1836
|
-
|
|
1752
|
+
this.completePartiallyCommittedElements();
|
|
1753
|
+
this.completePartiallyCommittedAspects();
|
|
1837
1754
|
}
|
|
1838
1755
|
/**
|
|
1839
1756
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1840
|
-
* are intending to process changes
|
|
1757
|
+
* are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called.
|
|
1841
1758
|
* @note Called by all `process*` functions implicitly.
|
|
1842
1759
|
* Overriders must call `super.initialize()` first
|
|
1843
1760
|
*/
|
|
1844
|
-
async initialize(
|
|
1761
|
+
async initialize() {
|
|
1845
1762
|
if (this._initialized)
|
|
1846
1763
|
return;
|
|
1847
|
-
|
|
1764
|
+
this.initScopeProvenance();
|
|
1765
|
+
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1848
1766
|
await this.context.initialize();
|
|
1849
1767
|
// need exporter initialized to do remapdeletedsourceentities.
|
|
1850
|
-
await this.exporter.initialize(this.getExportInitOpts(
|
|
1768
|
+
await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
|
|
1851
1769
|
// Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity).
|
|
1852
1770
|
await this.processChangesets();
|
|
1853
1771
|
this._initialized = true;
|
|
@@ -1913,7 +1831,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1913
1831
|
const changeType = change.$meta?.op;
|
|
1914
1832
|
if (changeType === "Deleted" &&
|
|
1915
1833
|
change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
|
|
1916
|
-
change.Scope.Id === this.targetScopeElementId
|
|
1834
|
+
change.Scope.Id === this.targetScopeElementId &&
|
|
1835
|
+
change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
|
|
1917
1836
|
elemIdToScopeEsa.set(change.Element.Id, change);
|
|
1918
1837
|
}
|
|
1919
1838
|
else if ((changeType === "Inserted" || changeType === "Updated") &&
|
|
@@ -2055,10 +1974,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2055
1974
|
this._csFileProps = [];
|
|
2056
1975
|
return;
|
|
2057
1976
|
}
|
|
1977
|
+
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
2058
1978
|
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
2059
1979
|
// to ignore those already processed changes
|
|
2060
|
-
const startChangesetIndexOrId =
|
|
2061
|
-
|
|
1980
|
+
const startChangesetIndexOrId = startChangeset?.index ??
|
|
1981
|
+
startChangeset?.id ??
|
|
2062
1982
|
this.synchronizationVersion.index + 1;
|
|
2063
1983
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
2064
1984
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
@@ -2068,11 +1988,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2068
1988
|
iModelId: this.sourceDb.iModelId,
|
|
2069
1989
|
// eslint-disable-next-line deprecation/deprecation
|
|
2070
1990
|
changeset: { id: indexOrId },
|
|
2071
|
-
accessToken: args.accessToken,
|
|
2072
1991
|
})
|
|
2073
1992
|
.then((changeset) => changeset.index)));
|
|
2074
1993
|
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
2075
|
-
if (!this._options.
|
|
1994
|
+
if (!this._options.argsForProcessChanges
|
|
1995
|
+
?.ignoreMissingChangesetsInSynchronizations &&
|
|
2076
1996
|
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
2077
1997
|
this.synchronizationVersion.index !== -1) {
|
|
2078
1998
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
@@ -2107,13 +2027,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2107
2027
|
this._sourceChangeDataState =
|
|
2108
2028
|
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2109
2029
|
}
|
|
2030
|
+
/**
|
|
2031
|
+
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2032
|
+
* @section When argsForProcessChanges are defined:
|
|
2033
|
+
*
|
|
2034
|
+
* Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2035
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2036
|
+
*
|
|
2037
|
+
* Notes:
|
|
2038
|
+
* - the transformer assumes that you saveChanges after processing changes. You should not modify the iModel after processChanges until saveChanges,
|
|
2039
|
+
* failure to do so may result in corrupted
|
|
2040
|
+
* data loss in future branch operations
|
|
2041
|
+
* - if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2042
|
+
* will automatically be determined and used
|
|
2043
|
+
* - To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
2044
|
+
*
|
|
2045
|
+
* @section When argsForProcessChanges are undefined:
|
|
2046
|
+
*
|
|
2047
|
+
* Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2048
|
+
*
|
|
2049
|
+
* Notes:
|
|
2050
|
+
* - [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2051
|
+
*
|
|
2052
|
+
*/
|
|
2053
|
+
async process() {
|
|
2054
|
+
await this.initialize();
|
|
2055
|
+
this.logSettings();
|
|
2056
|
+
return this._options.argsForProcessChanges !== undefined
|
|
2057
|
+
? this.processChanges(this._options.argsForProcessChanges)
|
|
2058
|
+
: this.processAll();
|
|
2059
|
+
}
|
|
2110
2060
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2111
2061
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2112
2062
|
*/
|
|
2113
|
-
async processAll(
|
|
2114
|
-
this.logSettings();
|
|
2115
|
-
this.initScopeProvenance();
|
|
2116
|
-
await this.initialize();
|
|
2063
|
+
async processAll() {
|
|
2117
2064
|
await this.exporter.exportCodeSpecs();
|
|
2118
2065
|
await this.exporter.exportFonts();
|
|
2119
2066
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
@@ -2125,9 +2072,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2125
2072
|
else {
|
|
2126
2073
|
await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
|
|
2127
2074
|
}
|
|
2075
|
+
this.completePartiallyCommittedElements();
|
|
2128
2076
|
await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
|
|
2077
|
+
this.completePartiallyCommittedAspects();
|
|
2129
2078
|
await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
2130
|
-
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
2131
2079
|
if (this._options.forceExternalSourceAspectProvenance &&
|
|
2132
2080
|
this.shouldDetectDeletes()) {
|
|
2133
2081
|
// eslint-disable-next-line deprecation/deprecation
|
|
@@ -2138,7 +2086,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2138
2086
|
if (this._options.optimizeGeometry)
|
|
2139
2087
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2140
2088
|
this.importer.computeProjectExtents();
|
|
2141
|
-
|
|
2089
|
+
this.finalizeTransformation();
|
|
2142
2090
|
}
|
|
2143
2091
|
markLastProvenance(sourceAspect, { isRelationship = false }) {
|
|
2144
2092
|
this._lastProvenanceEntityInfo =
|
|
@@ -2155,39 +2103,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2155
2103
|
}
|
|
2156
2104
|
/** Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2157
2105
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2158
|
-
* @note the transformer
|
|
2106
|
+
* @note the transformer assumes that you saveChanges after processing changes. You should not
|
|
2107
|
+
* modify the iModel after processChanges until saveChanges, failure to do so may result in corrupted
|
|
2108
|
+
* data loss in future branch operations
|
|
2159
2109
|
* @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2160
2110
|
* will automatically be determined and used
|
|
2161
2111
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
2162
2112
|
*/
|
|
2163
2113
|
async processChanges(options) {
|
|
2164
|
-
this._isSynchronization = true;
|
|
2165
|
-
this.initScopeProvenance();
|
|
2166
|
-
this.logSettings();
|
|
2167
|
-
await this.initialize(options);
|
|
2168
2114
|
// must wait for initialization of synchronization provenance data
|
|
2169
2115
|
await this.exporter.exportChanges(this.getExportInitOpts(options));
|
|
2170
|
-
|
|
2116
|
+
this.completePartiallyCommittedElements();
|
|
2117
|
+
this.completePartiallyCommittedAspects();
|
|
2171
2118
|
if (this._options.optimizeGeometry)
|
|
2172
2119
|
this.importer.optimizeGeometry(this._options.optimizeGeometry);
|
|
2173
2120
|
this.importer.computeProjectExtents();
|
|
2174
|
-
|
|
2121
|
+
this.finalizeTransformation();
|
|
2122
|
+
const defaultSaveTargetChanges = () => {
|
|
2123
|
+
this.targetDb.saveChanges();
|
|
2124
|
+
};
|
|
2125
|
+
await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
|
|
2175
2126
|
}
|
|
2176
2127
|
/** Changeset data must be initialized in order to build correct changeOptions.
|
|
2177
2128
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
2178
2129
|
*/
|
|
2179
2130
|
getExportInitOpts(opts) {
|
|
2180
|
-
if (!this.
|
|
2131
|
+
if (!this._options.argsForProcessChanges)
|
|
2181
2132
|
return {};
|
|
2133
|
+
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2182
2134
|
return {
|
|
2183
|
-
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements
|
|
2184
|
-
accessToken: opts.accessToken,
|
|
2135
|
+
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
2185
2136
|
...(this._csFileProps
|
|
2186
2137
|
? { csFileProps: this._csFileProps }
|
|
2187
2138
|
: this._changesetRanges
|
|
2188
2139
|
? { changesetRanges: this._changesetRanges }
|
|
2189
|
-
:
|
|
2190
|
-
? { startChangeset
|
|
2140
|
+
: startChangeset
|
|
2141
|
+
? { startChangeset }
|
|
2191
2142
|
: {
|
|
2192
2143
|
startChangeset: {
|
|
2193
2144
|
index: this.synchronizationVersion.index + 1,
|
|
@@ -2269,8 +2220,7 @@ class TemplateModelCloner extends IModelTransformer {
|
|
|
2269
2220
|
}
|
|
2270
2221
|
/** Cloning from a template requires this override of onTransformElement. */
|
|
2271
2222
|
onTransformElement(sourceElement) {
|
|
2272
|
-
|
|
2273
|
-
const referenceIds = sourceElement.getReferenceConcreteIds();
|
|
2223
|
+
const referenceIds = sourceElement.getReferenceIds();
|
|
2274
2224
|
referenceIds.forEach((referenceId) => {
|
|
2275
2225
|
// TODO: consider going through all definition elements at once and remapping them to themselves
|
|
2276
2226
|
if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {
|