@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.
@@ -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._isSynchronization) {
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
- /** map of partially committed entities to their partial commit progress */
226
- this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
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 ?? false,
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 processChanges when change history is known.
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 [[processAll]] when the option
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 using [[processChanges]] since changeset information is sufficient.
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
- static transformCallbackFor(transformer, entity) {
962
- if (entity instanceof core_backend_1.Element)
963
- return transformer.onTransformElement; // eslint-disable-line @typescript-eslint/unbound-method
964
- else if (entity instanceof core_backend_1.Element)
965
- return transformer.onTransformModel; // eslint-disable-line @typescript-eslint/unbound-method
966
- else if (entity instanceof core_backend_1.Relationship)
967
- return transformer.onTransformRelationship; // eslint-disable-line @typescript-eslint/unbound-method
968
- else if (entity instanceof core_backend_1.ElementAspect)
969
- return transformer.onTransformElementAspect; // eslint-disable-line @typescript-eslint/unbound-method
970
- else
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
- updateEntity({ ...targetProps, id: core_backend_1.EntityReferences.toId64(targetId) });
991
- this._partiallyCommittedEntities.delete(sourceEntity);
992
- };
935
+ const targetProps = this.onTransformElement(sourceElement);
936
+ this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
937
+ }
993
938
  }
994
- /** collect references this entity has that are yet to be mapped, and if there are any
995
- * create a [[PartiallyCommittedEntity]] to track resolution of those references
996
- */
997
- collectUnmappedReferences(entity) {
998
- const missingReferences = new core_common_1.EntityReferenceSet();
999
- let thisPartialElem;
1000
- // eslint-disable-next-line deprecation/deprecation
1001
- for (const referenceId of entity.getReferenceConcreteIds()) {
1002
- // TODO: probably need to rename from 'id' to 'ref' so these names aren't so ambiguous
1003
- const referenceIdInTarget = this.context.findTargetEntityId(referenceId);
1004
- const alreadyProcessed = core_backend_1.EntityReferences.isValid(referenceIdInTarget) ||
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
- if (!referencedExistsInSource) {
1013
- core_bentley_1.Logger.logWarning(loggerCategory, `Source ${EntityUnifier_1.EntityUnifier.getReadableType(entity)} (${entity.id}) has a dangling reference to (${referenceId})`);
1014
- switch (this._options.danglingReferencesBehavior) {
1015
- case "ignore":
1016
- continue;
1017
- case "reject":
1018
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
1019
- `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
1020
- "This must have been caused by an upstream application that changed the iModel.",
1021
- "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this, but this will leave the iModel",
1022
- "in a state where downstream consuming applications will need to handle the invalidity themselves. In some cases, writing a custom",
1023
- "transformer to remove the reference and fix affected elements may be suitable.",
1024
- ].join("\n"));
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 (thisPartialElem === undefined) {
1028
- thisPartialElem = new PartiallyCommittedEntity(missingReferences, this.makePartialEntityCompleter(entity));
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
- missingReferences.add(referenceId);
1033
- const entityReference = core_backend_1.EntityReferences.from(entity);
1034
- this._pendingReferences.set({ referenced: referenceId, referencer: entityReference }, thisPartialElem);
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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.preserveElementIdsForFiltering) {
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.collectUnmappedReferences(sourceElement);
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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
- /** Import elements that were deferred in a prior pass.
1403
- * @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
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 [[processChanges]] instead.
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 if you are not running processChanges in this transformation, this will fail
1414
- * without setting the `force` option to `true`
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({ force = false } = {}) {
1417
- const notForcedAndHasNoChangesAndIsntProvenanceInit = !force &&
1418
- this._sourceChangeDataState !== "has-changes" &&
1419
- !this._isProvenanceInitTransform;
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._isProvenanceInitTransform) {
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 if (!this.isReverseSynchronization) {
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._isSynchronization ||
1441
- (this._startingChangesetIndices && this._isProvenanceInitTransform)) {
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
- const [syncChangesetsToClearKey, syncChangesetsToUpdateKey] = this
1450
- .isReverseSynchronization
1451
- ? [
1452
- pendingReverseSyncChangesetIndicesKey,
1453
- pendingSyncChangesetIndicesKey,
1454
- ]
1455
- : [
1456
- pendingSyncChangesetIndicesKey,
1457
- pendingReverseSyncChangesetIndicesKey,
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
- async finalizeTransformation(options) {
1432
+ finalizeTransformation() {
1482
1433
  this.importer.finalize();
1483
- this.updateSynchronizationVersion();
1484
- if (this._partiallyCommittedEntities.size > 0) {
1485
- const message = [
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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 processChanges
1612
- * @note This method is called from [[processAll]] and is not needed by [[processChanges]], so it only needs to be called directly when processing a subset of an iModel.
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 targetElementId = this.context.findTargetElementId(sourceAspect.element.id);
1680
- const targetAspectProps = this.onTransformElementAspect(sourceAspect, targetElementId);
1681
- this.collectUnmappedReferences(sourceAspect);
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, targetElementId));
1694
- sourceAspects.forEach((a) => this.collectUnmappedReferences(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, _targetElementId) {
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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 [[processChanges]] and [[processAll]], so it only needs to be called directly when processing a subset of an iModel.
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
- return this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
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, but prefer using [[processChanges]] explicitly since it calls this.
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(args) {
1761
+ async initialize() {
1845
1762
  if (this._initialized)
1846
1763
  return;
1847
- await this._tryInitChangesetData(args);
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(args ?? {}));
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 = args.startChangeset?.index ??
2061
- args.startChangeset?.id ??
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.ignoreMissingChangesetsInSynchronizations &&
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(options) {
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
- await this.finalizeTransformation(options);
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 saves and pushes changes when its work is complete.
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
- await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
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
- await this.finalizeTransformation(options);
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._isSynchronization)
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 ?? false,
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
- : opts.startChangeset
2190
- ? { startChangeset: opts.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
- // eslint-disable-next-line deprecation/deprecation
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))) {