@itwin/imodel-transformer 1.0.0-dev.2 → 1.0.0-dev.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/lib/cjs/Algo.d.ts +7 -0
  3. package/lib/cjs/Algo.d.ts.map +1 -1
  4. package/lib/cjs/Algo.js +7 -0
  5. package/lib/cjs/Algo.js.map +1 -1
  6. package/lib/cjs/BigMap.d.ts +6 -1
  7. package/lib/cjs/BigMap.d.ts.map +1 -1
  8. package/lib/cjs/BigMap.js +28 -2
  9. package/lib/cjs/BigMap.js.map +1 -1
  10. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  11. package/lib/cjs/BranchProvenanceInitializer.js +2 -0
  12. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  13. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  14. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  15. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
  16. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  17. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  18. package/lib/cjs/EntityUnifier.js.map +1 -1
  19. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  20. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  21. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  22. package/lib/cjs/IModelCloneContext.js +16 -1
  23. package/lib/cjs/IModelCloneContext.js.map +1 -1
  24. package/lib/cjs/IModelExporter.d.ts +5 -6
  25. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  26. package/lib/cjs/IModelExporter.js +15 -12
  27. package/lib/cjs/IModelExporter.js.map +1 -1
  28. package/lib/cjs/IModelImporter.d.ts +27 -4
  29. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  30. package/lib/cjs/IModelImporter.js +61 -23
  31. package/lib/cjs/IModelImporter.js.map +1 -1
  32. package/lib/cjs/IModelTransformer.d.ts +113 -150
  33. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  34. package/lib/cjs/IModelTransformer.js +377 -340
  35. package/lib/cjs/IModelTransformer.js.map +1 -1
  36. package/lib/cjs/transformer.js +2 -1
  37. package/lib/cjs/transformer.js.map +1 -1
  38. package/package.json +17 -16
  39. package/lib/cjs/PendingReferenceMap.d.ts +0 -37
  40. package/lib/cjs/PendingReferenceMap.d.ts.map +0 -1
  41. package/lib/cjs/PendingReferenceMap.js +0 -92
  42. package/lib/cjs/PendingReferenceMap.js.map +0 -1
@@ -13,13 +13,12 @@ const Semver = require("semver");
13
13
  const nodeAssert = require("assert");
14
14
  const core_bentley_1 = require("@itwin/core-bentley");
15
15
  const core_geometry_1 = require("@itwin/core-geometry");
16
+ const coreBackendPkgJson = require("@itwin/core-backend/package.json");
16
17
  const core_backend_1 = require("@itwin/core-backend");
17
18
  const core_common_1 = require("@itwin/core-common");
18
19
  const IModelExporter_1 = require("./IModelExporter");
19
20
  const IModelImporter_1 = require("./IModelImporter");
20
21
  const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
21
- const PendingReferenceMap_1 = require("./PendingReferenceMap");
22
- const EntityMap_1 = require("./EntityMap");
23
22
  const IModelCloneContext_1 = require("./IModelCloneContext");
24
23
  const EntityUnifier_1 = require("./EntityUnifier");
25
24
  const Algo_1 = require("./Algo");
@@ -30,30 +29,6 @@ const nullLastProvenanceEntityInfo = {
30
29
  aspectVersion: "",
31
30
  aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
32
31
  };
33
- /**
34
- * A container for tracking the state of a partially committed entity and finalizing it when it's ready to be fully committed
35
- * @internal
36
- */
37
- class PartiallyCommittedEntity {
38
- constructor(
39
- /**
40
- * A set of "model|element ++ ID64" pairs, (e.g. `model0x11` or `element0x12`)
41
- * It is possible for the submodel of an element to be separately resolved from the actual element,
42
- * so its resolution must be tracked separately
43
- */
44
- _missingReferences, _onComplete) {
45
- this._missingReferences = _missingReferences;
46
- this._onComplete = _onComplete;
47
- }
48
- resolveReference(id) {
49
- this._missingReferences.delete(id);
50
- if (this._missingReferences.size === 0)
51
- this._onComplete();
52
- }
53
- forceComplete() {
54
- this._onComplete();
55
- }
56
- }
57
32
  /**
58
33
  * Apply a function to each Id64 in a supported container type of Id64s.
59
34
  * Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
@@ -80,7 +55,7 @@ function mapId64(idContainer, func) {
80
55
  }
81
56
  else {
82
57
  throw Error([
83
- `Id64 container '${idContainer}' is unsupported.`,
58
+ `Id64 container '${JSON.stringify(idContainer)}' is unsupported.`,
84
59
  "Currently only singular Id64 strings or prop-like objects containing an 'id' property are supported.",
85
60
  ].join("\n"));
86
61
  }
@@ -149,7 +124,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
149
124
  id: targetScopeElementId,
150
125
  relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
151
126
  },
152
- scope: { id: core_common_1.IModel.rootSubjectId },
127
+ scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
153
128
  identifier: sourceDb.iModelId,
154
129
  kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
155
130
  jsonProperties: undefined,
@@ -171,7 +146,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
171
146
  if (this._isProvenanceInitTransform) {
172
147
  return "forward";
173
148
  }
174
- if (!this._isSynchronization) {
149
+ if (!this._options.argsForProcessChanges) {
175
150
  return "not-sync";
176
151
  }
177
152
  try {
@@ -216,14 +191,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
216
191
  */
217
192
  constructor(source, target, options) {
218
193
  super();
219
- /** map of (unprocessed element, referencing processed element) pairs to the partially committed element that needs the reference resolved
220
- * and have some helper methods below for now */
221
- this._pendingReferences = new PendingReferenceMap_1.PendingReferenceMap();
222
194
  /** a set of elements for which source provenance will be explicitly tracked by ExternalSourceAspects */
223
195
  this._elementsWithExplicitlyTrackedProvenance = new Set();
224
- /** map of partially committed entities to their partial commit progress */
225
- this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
226
- this._isSynchronization = false;
196
+ this._partiallyCommittedElementIds = new Set();
197
+ this._partiallyCommittedAspectIds = new Set();
227
198
  /**
228
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.
229
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.
@@ -232,10 +203,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
232
203
  */
233
204
  this._allowNoScopingESA = false;
234
205
  this._changesetRanges = undefined;
235
- /** Set of entity keys which were not exported and don't need to be tracked for pending reference resolution.
236
- * @note Currently only tracks elements which were not exported.
237
- */
238
- this._skippedEntities = new Set();
239
206
  /**
240
207
  * Previously the transformer would insert provenance always pointing to the "target" relationship.
241
208
  * It should (and now by default does) instead insert provenance pointing to the provenanceSource
@@ -273,11 +240,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
273
240
  cloneUsingBinaryGeometry: options?.cloneUsingBinaryGeometry ?? true,
274
241
  targetScopeElementId: options?.targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
275
242
  // eslint-disable-next-line deprecation/deprecation
276
- danglingReferencesBehavior: options?.danglingReferencesBehavior ??
277
- options?.danglingPredecessorsBehavior ??
278
- "reject",
243
+ danglingReferencesBehavior: options?.danglingReferencesBehavior ?? "reject",
279
244
  branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
245
+ skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
280
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
+ }
281
251
  this._isProvenanceInitTransform = this._options
282
252
  .wasSourceIModelCopiedToTarget
283
253
  ? true
@@ -361,8 +331,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
361
331
  core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
362
332
  core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
363
333
  core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
364
- core_bentley_1.Logger.logInfo(loggerCategory, `this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
365
- core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${this.importer.options.autoExtendProjectExtents}`);
334
+ core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
366
335
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
367
336
  }
368
337
  /** Return the IModelDb where IModelTransformer will store its provenance.
@@ -455,29 +424,30 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
455
424
  forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
456
425
  });
457
426
  }
458
- /** the changeset in the scoping element's source version found for this transformation
459
- * @note: the version depends on whether this is a reverse synchronization or not, as
460
- * it is stored separately for both synchronization directions.
461
- * @note: must call [[initScopeProvenance]] before using this property.
462
- * @note: empty string and -1 for changeset and index if it has never been transformed or was transformed before federation guid update (pre 1.x).
427
+ /**
428
+ * As of itwinjs 4.6.0, definitionContainers are now deleted as if they were DefinitionPartitions as opposed to Definitions.
429
+ * This variable being true will be used to special case the deletion of DefinitionContainers the same way DefinitionPartitions are deleted.
463
430
  */
464
- get _synchronizationVersion() {
465
- if (!this._cachedSynchronizationVersion) {
466
- nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps was not set yet");
467
- const version = this.isReverseSynchronization
468
- ? this._targetScopeProvenanceProps.jsonProperties?.reverseSyncVersion
469
- : this._targetScopeProvenanceProps.version;
470
- nodeAssert(version !== undefined, "no version contained in target scope");
471
- const [id, index] = version === "" ? ["", -1] : version.split(";");
472
- this._cachedSynchronizationVersion = { index: Number(index), id };
473
- nodeAssert(!Number.isNaN(this._cachedSynchronizationVersion.index), "bad parse: invalid index in version");
431
+ get hasDefinitionContainerDeletionFeature() {
432
+ if (this._hasDefinitionContainerDeletionFeature === undefined) {
433
+ this._hasDefinitionContainerDeletionFeature = Semver.satisfies(coreBackendPkgJson.version, "^4.6.0");
474
434
  }
475
- return this._cachedSynchronizationVersion;
435
+ return this._hasDefinitionContainerDeletionFeature;
436
+ }
437
+ /**
438
+ * We cache the synchronization version to avoid querying the target scoping ESA multiple times.
439
+ * If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
440
+ * Sets this._cachedSynchronizationVersion to undefined.
441
+ */
442
+ clearCachedSynchronizationVersion() {
443
+ this._cachedSynchronizationVersion = undefined;
476
444
  }
477
445
  /** the changeset in the scoping element's source version found for this transformation
478
- * @note: the version depends on whether this is a reverse synchronization or not, as
446
+ * @note the version depends on whether this is a reverse synchronization or not, as
479
447
  * it is stored separately for both synchronization directions.
480
- * @note: empty string and -1 for changeset and index if it has never been transformed, or was transformed before federation guid update (pre 1.x).
448
+ * @note empty string and -1 for changeset and index if it has never been transformed
449
+ * @note empty string and -1 for changeset and index if it was transformed before federation guid update (pre 1.x) and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] === "unsafe-migrate".
450
+ * @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
481
451
  */
482
452
  get synchronizationVersion() {
483
453
  if (this._cachedSynchronizationVersion === undefined) {
@@ -488,10 +458,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
488
458
  const version = this.isReverseSynchronization
489
459
  ? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
490
460
  : provenanceScopeAspect.version;
491
- if (!version) {
461
+ if (!version &&
462
+ this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
492
463
  return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
493
464
  }
494
- const [id, index] = version.split(";");
465
+ if (version === undefined) {
466
+ throw new Error(`Could not find synchronization version in scope aspect. This may be due to the last successful run of the transformer being done with an older version.
467
+ Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
468
+ }
469
+ const [id, index] = version === "" ? ["", -1] : version.split(";");
495
470
  if (Number.isNaN(Number(index)))
496
471
  throw new Error("Could not parse version data from scope aspect");
497
472
  this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
@@ -530,7 +505,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
530
505
  id: this.targetScopeElementId,
531
506
  relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
532
507
  },
533
- scope: { id: core_common_1.IModel.rootSubjectId },
508
+ scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
534
509
  identifier: this.provenanceSourceDb.iModelId,
535
510
  kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
536
511
  jsonProperties: undefined,
@@ -567,29 +542,82 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
567
542
  jsonProperties: JSON.stringify(aspectProps.jsonProperties),
568
543
  });
569
544
  aspectProps.id = id;
545
+ // Busting a potential cached version
546
+ this.clearCachedSynchronizationVersion();
570
547
  }
571
548
  }
572
549
  else {
573
550
  // foundEsaProps is defined.
574
551
  aspectProps.id = foundEsaProps.aspectId;
575
- aspectProps.version =
576
- foundEsaProps.version ??
577
- (this._options.branchRelationshipDataBehavior === "unsafe-migrate"
578
- ? ""
579
- : undefined);
552
+ aspectProps.version = foundEsaProps.version;
580
553
  aspectProps.jsonProperties = foundEsaProps.jsonProperties
581
554
  ? JSON.parse(foundEsaProps.jsonProperties)
582
- : this._options.branchRelationshipDataBehavior === "unsafe-migrate"
583
- ? {
584
- pendingReverseSyncChangesetIndices: [],
585
- pendingSyncChangesetIndices: [],
586
- reverseSyncVersion: "",
587
- }
588
- : undefined;
555
+ : undefined;
556
+ // Clone oldProps incase they're changed for logging purposes
557
+ const oldProps = JSON.parse(JSON.stringify(aspectProps));
558
+ if (this.handleUnsafeMigrate(aspectProps)) {
559
+ core_bentley_1.Logger.logInfo(loggerCategory, "Unsafe migrate made a change to the target scope's external source aspect. Updating aspect in database.", { oldProps, newProps: aspectProps });
560
+ this.provenanceDb.elements.updateAspect({
561
+ ...aspectProps,
562
+ jsonProperties: JSON.stringify(aspectProps.jsonProperties),
563
+ });
564
+ // Busting a potential cached version
565
+ this.clearCachedSynchronizationVersion();
566
+ }
589
567
  }
590
568
  this._targetScopeProvenanceProps =
591
569
  aspectProps;
592
570
  }
571
+ /** Returns true if a change was made to the aspectProps. */
572
+ handleUnsafeMigrate(aspectProps) {
573
+ let madeChange = false;
574
+ if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
575
+ return madeChange;
576
+ const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
577
+ const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
578
+ "";
579
+ if (aspectProps.version === undefined ||
580
+ (aspectProps.version === "" &&
581
+ aspectProps.version !== fallbackSyncVersionToUse)) {
582
+ aspectProps.version = fallbackSyncVersionToUse;
583
+ madeChange = true;
584
+ }
585
+ if (aspectProps.jsonProperties === undefined) {
586
+ aspectProps.jsonProperties = {
587
+ pendingReverseSyncChangesetIndices: [],
588
+ pendingSyncChangesetIndices: [],
589
+ reverseSyncVersion: fallbackReverseSyncVersionToUse,
590
+ };
591
+ madeChange = true;
592
+ }
593
+ else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
594
+ (aspectProps.jsonProperties.reverseSyncVersion === "" &&
595
+ aspectProps.jsonProperties.reverseSyncVersion !==
596
+ fallbackReverseSyncVersionToUse)) {
597
+ aspectProps.jsonProperties.reverseSyncVersion =
598
+ fallbackReverseSyncVersionToUse;
599
+ madeChange = true;
600
+ }
601
+ /**
602
+ * This case will only be hit when:
603
+ * - first transformation was performed on pre-fedguid transformer.
604
+ * - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
605
+ * - change processing was invoked on for the second 'initial' transformation.
606
+ * NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
607
+ */
608
+ if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
609
+ undefined) {
610
+ core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
611
+ aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
612
+ madeChange = true;
613
+ }
614
+ if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
615
+ core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
616
+ aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
617
+ madeChange = true;
618
+ }
619
+ return madeChange;
620
+ }
593
621
  /**
594
622
  * Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
595
623
  * and call a function for each one.
@@ -685,7 +713,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
685
713
  targetScopeElementId: this.targetScopeElementId,
686
714
  isReverseSynchronization: this.isReverseSynchronization,
687
715
  fn,
688
- skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? false,
716
+ skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
689
717
  });
690
718
  }
691
719
  /**
@@ -807,7 +835,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
807
835
  }
808
836
  /** Returns `true` if *brute force* delete detections should be run.
809
837
  * @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
810
- * @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.
811
839
  */
812
840
  shouldDetectDeletes() {
813
841
  nodeAssert(this._syncType !== undefined);
@@ -817,9 +845,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
817
845
  * Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
818
846
  * in the source iModel.
819
847
  * @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
820
- * 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
821
849
  * [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
822
- * necessary when using [[processChanges]] since changeset information is sufficient.
850
+ * necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
823
851
  * @note you do not need to call this directly unless processing a subset of an iModel.
824
852
  * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
825
853
  */
@@ -849,12 +877,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
849
877
  }
850
878
  });
851
879
  }
852
- /**
853
- * @deprecated in 3.x, this no longer has any effect except emitting a warning
854
- */
855
- skipElement(_sourceElement) {
856
- core_bentley_1.Logger.logWarning(loggerCategory, "Tried to defer/skip an element, which is no longer necessary");
857
- }
858
880
  /** Transform the specified sourceElement into ElementProps for the target iModel.
859
881
  * @param sourceElement The Element from the source iModel to transform.
860
882
  * @returns ElementProps for the target iModel.
@@ -888,10 +910,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
888
910
  }
889
911
  /** Returns true if a change within sourceElement is detected.
890
912
  * @param sourceElement The Element from the source iModel
891
- * @param targetElementId The Element from the target iModel to compare against.
892
913
  * @note A subclass can override this method to provide custom change detection behavior.
893
914
  */
894
- hasElementChanged(sourceElement, _targetElementId) {
915
+ hasElementChanged(sourceElement) {
895
916
  if (this._sourceChangeDataState === "no-changes")
896
917
  return false;
897
918
  if (this._sourceChangeDataState === "unconnected")
@@ -900,85 +921,74 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
900
921
  nodeAssert(this._hasElementChangedCache !== undefined, "has element changed cache should be initialized by now");
901
922
  return this._hasElementChangedCache.has(sourceElement.id);
902
923
  }
903
- static transformCallbackFor(transformer, entity) {
904
- if (entity instanceof core_backend_1.Element)
905
- return transformer.onTransformElement; // eslint-disable-line @typescript-eslint/unbound-method
906
- else if (entity instanceof core_backend_1.Element)
907
- return transformer.onTransformModel; // eslint-disable-line @typescript-eslint/unbound-method
908
- else if (entity instanceof core_backend_1.Relationship)
909
- return transformer.onTransformRelationship; // eslint-disable-line @typescript-eslint/unbound-method
910
- else if (entity instanceof core_backend_1.ElementAspect)
911
- return transformer.onTransformElementAspect; // eslint-disable-line @typescript-eslint/unbound-method
912
- else
913
- (0, core_bentley_1.assert)(false, `unreachable; entity was '${entity.constructor.name}' not an Element, Relationship, or ElementAspect`);
914
- }
915
- /** callback to perform when a partial element says it's ready to be completed
916
- * transforms the source element with all references now valid, then updates the partial element with the results
917
- */
918
- makePartialEntityCompleter(sourceEntity) {
919
- return () => {
920
- const targetId = this.context.findTargetEntityId(core_backend_1.EntityReferences.from(sourceEntity));
921
- if (!core_backend_1.EntityReferences.isValid(targetId))
922
- throw Error(`${sourceEntity.id} has not been inserted into the target yet, the completer is invalid. This is a bug.`);
923
- const onEntityTransform = IModelTransformer.transformCallbackFor(this, sourceEntity);
924
- const updateEntity = EntityUnifier_1.EntityUnifier.updaterFor(this.targetDb, sourceEntity);
925
- const targetProps = onEntityTransform.call(this, sourceEntity);
926
- if (sourceEntity instanceof core_backend_1.Relationship) {
927
- targetProps.sourceId =
928
- this.context.findTargetElementId(sourceEntity.sourceId);
929
- targetProps.targetId =
930
- 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.`);
931
934
  }
932
- updateEntity({ ...targetProps, id: core_backend_1.EntityReferences.toId64(targetId) });
933
- this._partiallyCommittedEntities.delete(sourceEntity);
934
- };
935
+ const targetProps = this.onTransformElement(sourceElement);
936
+ this.targetDb.elements.updateElement({ ...targetProps, id: targetId });
937
+ }
935
938
  }
936
- /** collect references this entity has that are yet to be mapped, and if there are any
937
- * create a [[PartiallyCommittedEntity]] to track resolution of those references
938
- */
939
- collectUnmappedReferences(entity) {
940
- const missingReferences = new core_common_1.EntityReferenceSet();
941
- let thisPartialElem;
942
- // eslint-disable-next-line deprecation/deprecation
943
- for (const referenceId of entity.getReferenceConcreteIds()) {
944
- // TODO: probably need to rename from 'id' to 'ref' so these names aren't so ambiguous
945
- const referenceIdInTarget = this.context.findTargetEntityId(referenceId);
946
- const alreadyProcessed = core_backend_1.EntityReferences.isValid(referenceIdInTarget) ||
947
- this._skippedEntities.has(referenceId);
948
- if (alreadyProcessed)
949
- continue;
950
- core_bentley_1.Logger.logTrace(loggerCategory, `Deferring resolution of reference '${referenceId}' of element '${entity.id}'`);
951
- const referencedExistsInSource = EntityUnifier_1.EntityUnifier.exists(this.sourceDb, {
952
- 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,
953
950
  });
954
- if (!referencedExistsInSource) {
955
- core_bentley_1.Logger.logWarning(loggerCategory, `Source ${EntityUnifier_1.EntityUnifier.getReadableType(entity)} (${entity.id}) has a dangling reference to (${referenceId})`);
956
- switch (this._options.danglingReferencesBehavior) {
957
- case "ignore":
958
- continue;
959
- case "reject":
960
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, [
961
- `Found a reference to an element "${referenceId}" that doesn't exist while looking for references of "${entity.id}".`,
962
- "This must have been caused by an upstream application that changed the iModel.",
963
- "You can set the IModelTransformOptions.danglingReferencesBehavior option to 'ignore' to ignore this, but this will leave the iModel",
964
- "in a state where downstream consuming applications will need to handle the invalidity themselves. In some cases, writing a custom",
965
- "transformer to remove the reference and fix affected elements may be suitable.",
966
- ].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;
967
967
  }
968
+ allReferencesExist = false;
968
969
  }
969
- if (thisPartialElem === undefined) {
970
- thisPartialElem = new PartiallyCommittedEntity(missingReferences, this.makePartialEntityCompleter(entity));
971
- if (!this._partiallyCommittedEntities.has(entity))
972
- this._partiallyCommittedEntities.set(entity, thisPartialElem);
970
+ if (this._options.danglingReferencesBehavior === "reject") {
971
+ this.assertReferenceExistsInSource(referenceId, entity);
973
972
  }
974
- missingReferences.add(referenceId);
975
- const entityReference = core_backend_1.EntityReferences.from(entity);
976
- 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"));
977
987
  }
978
988
  }
979
989
  /** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
980
990
  * @param sourceElementId Identifies the Element from the source iModel to import.
981
- * @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.
982
992
  */
983
993
  async processElement(sourceElementId) {
984
994
  await this.initialize();
@@ -989,7 +999,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
989
999
  }
990
1000
  /** Import child elements into the target IModelDb
991
1001
  * @param sourceElementId Import the child elements of this element in the source IModelDb.
992
- * @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.
993
1003
  */
994
1004
  async processChildElements(sourceElementId) {
995
1005
  await this.initialize();
@@ -1001,24 +1011,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1001
1011
  shouldExportElement(_sourceElement) {
1002
1012
  return true;
1003
1013
  }
1004
- onSkipElement(sourceElementId) {
1005
- if (this.context.findTargetElementId(sourceElementId) !== core_bentley_1.Id64.invalid) {
1006
- // element already has provenance
1007
- return;
1008
- }
1009
- core_bentley_1.Logger.logInfo(loggerCategory, `Element '${sourceElementId}' won't be exported. Marking its references as resolved`);
1010
- const elementKey = `e${sourceElementId}`;
1011
- this._skippedEntities.add(elementKey);
1012
- // Mark any existing pending references to the skipped element as resolved.
1013
- for (const referencer of this._pendingReferences.getReferencersByEntityKey(elementKey)) {
1014
- const key = PendingReferenceMap_1.PendingReference.from(referencer, elementKey);
1015
- const pendingRef = this._pendingReferences.get(key);
1016
- if (!pendingRef)
1017
- continue;
1018
- pendingRef.resolveReference(elementKey);
1019
- this._pendingReferences.delete(key);
1020
- }
1021
- }
1022
1014
  /**
1023
1015
  * If they haven't been already, import all of the required references
1024
1016
  * @internal do not call, override or implement this, it will be removed
@@ -1085,11 +1077,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1085
1077
  onExportElement(sourceElement) {
1086
1078
  let targetElementId;
1087
1079
  let targetElementProps;
1088
- if (this._options.preserveElementIdsForFiltering) {
1089
- targetElementId = sourceElement.id;
1090
- targetElementProps = this.onTransformElement(sourceElement);
1091
- }
1092
- else if (this._options.wasSourceIModelCopiedToTarget) {
1080
+ if (this._options.wasSourceIModelCopiedToTarget) {
1093
1081
  targetElementId = sourceElement.id;
1094
1082
  targetElementProps =
1095
1083
  this.targetDb.elements.getElementProps(targetElementId);
@@ -1126,20 +1114,42 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1126
1114
  }
1127
1115
  }
1128
1116
  }
1129
- if (core_bentley_1.Id64.isValid(targetElementId) &&
1130
- !this.hasElementChanged(sourceElement, targetElementId))
1117
+ if (!this.hasElementChanged(sourceElement))
1131
1118
  return;
1132
- this.collectUnmappedReferences(sourceElement);
1119
+ if (!this.doAllReferencesExistInTarget(sourceElement)) {
1120
+ this._partiallyCommittedElementIds.add(sourceElement.id);
1121
+ }
1133
1122
  // targetElementId will be valid (indicating update) or undefined (indicating insert)
1134
1123
  targetElementProps.id = core_bentley_1.Id64.isValid(targetElementId)
1135
1124
  ? targetElementId
1136
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
+ }
1137
1149
  if (!this._options.wasSourceIModelCopiedToTarget) {
1138
1150
  this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
1139
1151
  }
1140
1152
  this.context.remapElement(sourceElement.id, targetElementProps.id); // targetElementProps.id assigned by importElement
1141
- // now that we've mapped this elem we can fix unmapped references to it
1142
- this.resolvePendingReferences(sourceElement);
1143
1153
  // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
1144
1154
  // insert external source aspects because federation guids are sufficient for this.
1145
1155
  // Other transformer subclasses must insert the appropriate aspect (as provided by a TBD API)
@@ -1167,16 +1177,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1167
1177
  this.markLastProvenance(provenance, { isRelationship: false });
1168
1178
  }
1169
1179
  }
1170
- resolvePendingReferences(entity) {
1171
- for (const referencer of this._pendingReferences.getReferencers(entity)) {
1172
- const key = PendingReferenceMap_1.PendingReference.from(referencer, entity);
1173
- const pendingRef = this._pendingReferences.get(key);
1174
- if (!pendingRef)
1175
- continue;
1176
- pendingRef.resolveReference(core_backend_1.EntityReferences.from(entity));
1177
- this._pendingReferences.delete(key);
1178
- }
1179
- }
1180
1180
  /** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
1181
1181
  * This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
1182
1182
  */
@@ -1196,12 +1196,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1196
1196
  const targetModeledElementId = this.context.findTargetElementId(sourceModel.id);
1197
1197
  // there can only be one repositoryModel per database, so ignore the repo model on remapped subjects
1198
1198
  const isRemappedRootSubject = sourceModel.id === core_common_1.IModel.repositoryModelId &&
1199
- targetModeledElementId != sourceModel.id;
1199
+ targetModeledElementId !== sourceModel.id;
1200
1200
  if (isRemappedRootSubject)
1201
1201
  return;
1202
1202
  const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
1203
1203
  this.importer.importModel(targetModelProps);
1204
- this.resolvePendingReferences(sourceModel);
1205
1204
  }
1206
1205
  /** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
1207
1206
  onDeleteModel(sourceModelId) {
@@ -1211,13 +1210,28 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1211
1210
  const targetModelId = this.context.findTargetElementId(sourceModelId);
1212
1211
  if (!core_bentley_1.Id64.isValidId64(targetModelId))
1213
1212
  return;
1213
+ let sql;
1214
+ if (this.hasDefinitionContainerDeletionFeature) {
1215
+ sql = `
1216
+ SELECT 1
1217
+ FROM bis.DefinitionPartition
1218
+ WHERE ECInstanceId=:targetModelId
1219
+ UNION
1220
+ SELECT 1
1221
+ FROM bis.DefinitionContainer
1222
+ WHERE ECInstanceId=:targetModelId
1223
+ `;
1224
+ }
1225
+ else {
1226
+ sql = `
1227
+ SELECT 1
1228
+ FROM bis.DefinitionPartition
1229
+ WHERE ECInstanceId=:targetModelId
1230
+ `;
1231
+ }
1214
1232
  if (this.exporter.sourceDbChanges?.element.deleteIds.has(sourceModelId)) {
1215
- const isDefinitionPartition = this.targetDb.withPreparedStatement(`
1216
- SELECT 1
1217
- FROM bis.DefinitionPartition
1218
- WHERE ECInstanceId=?
1219
- `, (stmt) => {
1220
- stmt.bindId(1, targetModelId);
1233
+ const isDefinitionPartition = this.targetDb.withPreparedStatement(sql, (stmt) => {
1234
+ stmt.bindId("targetModelId", targetModelId);
1221
1235
  const val = stmt.step();
1222
1236
  switch (val) {
1223
1237
  case core_bentley_1.DbResult.BE_SQLITE_ROW:
@@ -1225,7 +1239,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1225
1239
  case core_bentley_1.DbResult.BE_SQLITE_DONE:
1226
1240
  return false;
1227
1241
  default:
1228
- (0, core_bentley_1.assert)(false, `unexpected db result: '${stmt}'`);
1242
+ (0, core_bentley_1.assert)(false, `unexpected db result: '${JSON.stringify(stmt)}'`);
1229
1243
  }
1230
1244
  });
1231
1245
  if (isDefinitionPartition) {
@@ -1260,7 +1274,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1260
1274
  }
1261
1275
  /** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
1262
1276
  * @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
1263
- * @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.
1264
1278
  */
1265
1279
  async processModel(sourceModeledElementId) {
1266
1280
  await this.initialize();
@@ -1270,7 +1284,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1270
1284
  * @param sourceModelId Import the contents of this model from the source IModelDb.
1271
1285
  * @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
1272
1286
  * @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
1273
- * @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.
1274
1288
  */
1275
1289
  async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
1276
1290
  await this.initialize();
@@ -1327,46 +1341,50 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1327
1341
  targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
1328
1342
  return targetModelProps;
1329
1343
  }
1330
- /** Import elements that were deferred in a prior pass.
1331
- * @deprecated in 3.x. This method is no longer necessary since the transformer no longer needs to defer elements
1332
- */
1333
- async processDeferredElements(_numRetries = 3) { }
1334
- /** called at the end ([[finalizeTransformation]]) of a transformation,
1344
+ /**
1345
+ * Called at the end of a transformation,
1335
1346
  * updates the target scope element to say that transformation up through the
1336
1347
  * source's changeset has been performed. Also stores all changesets that occurred
1337
1348
  * during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
1338
1349
  *
1339
- * 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.
1340
1351
  * It is public for unsupported use cases of custom synchronization transforms.
1341
- * @note if you are not running processChanges in this transformation, this will fail
1342
- * 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.
1343
1361
  */
1344
- updateSynchronizationVersion({ force = false } = {}) {
1345
- const notForcedAndHasNoChangesAndIsntProvenanceInit = !force &&
1346
- this._sourceChangeDataState !== "has-changes" &&
1347
- !this._isProvenanceInitTransform;
1348
- if (notForcedAndHasNoChangesAndIsntProvenanceInit)
1362
+ updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
1363
+ const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
1364
+ this._sourceChangeDataState !== "has-changes";
1365
+ if (shouldSkipSyncVersionUpdate)
1349
1366
  return;
1350
1367
  nodeAssert(this._targetScopeProvenanceProps);
1351
1368
  const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
1352
1369
  const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
1353
- if (this._isProvenanceInitTransform) {
1354
- this._targetScopeProvenanceProps.version = sourceVersion;
1355
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1356
- targetVersion;
1357
- }
1358
- else if (this.isReverseSynchronization) {
1370
+ if (this.isReverseSynchronization) {
1359
1371
  const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
1360
1372
  core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
1361
1373
  this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1362
1374
  sourceVersion;
1363
1375
  }
1364
- else if (!this.isReverseSynchronization) {
1376
+ else {
1365
1377
  core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
1366
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
+ }
1367
1385
  }
1368
- if (this._isSynchronization ||
1369
- (this._startingChangesetIndices && this._isProvenanceInitTransform)) {
1386
+ if (this._options.argsForProcessChanges ||
1387
+ (this._startingChangesetIndices && initializeReverseSyncVersion)) {
1370
1388
  nodeAssert(this.targetDb.changeset.index !== undefined &&
1371
1389
  this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
1372
1390
  const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
@@ -1374,16 +1392,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1374
1392
  core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1375
1393
  const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
1376
1394
  const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
1377
- const [syncChangesetsToClearKey, syncChangesetsToUpdateKey] = this
1378
- .isReverseSynchronization
1379
- ? [
1380
- pendingReverseSyncChangesetIndicesKey,
1381
- pendingSyncChangesetIndicesKey,
1382
- ]
1383
- : [
1384
- pendingSyncChangesetIndicesKey,
1385
- pendingReverseSyncChangesetIndicesKey,
1386
- ];
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
1387
1410
  for (let i = this._startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
1388
1411
  jsonProps[syncChangesetsToUpdateKey].push(i);
1389
1412
  // Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
@@ -1403,25 +1426,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1403
1426
  ...this._targetScopeProvenanceProps,
1404
1427
  jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
1405
1428
  });
1429
+ this.clearCachedSynchronizationVersion();
1406
1430
  }
1407
1431
  // FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
1408
- async finalizeTransformation(options) {
1432
+ finalizeTransformation() {
1409
1433
  this.importer.finalize();
1410
- this.updateSynchronizationVersion();
1411
- if (this._partiallyCommittedEntities.size > 0) {
1412
- const message = [
1413
- "The following elements were never fully resolved:",
1414
- [...this._partiallyCommittedEntities.keys()].join(","),
1415
- "This indicates that either some references were excluded from the transformation",
1416
- "or the source has dangling references.",
1417
- ].join("\n");
1418
- if (this._options.danglingReferencesBehavior === "reject")
1419
- throw new Error(message);
1420
- core_bentley_1.Logger.logWarning(loggerCategory, message);
1421
- for (const partiallyCommittedElem of this._partiallyCommittedEntities.values()) {
1422
- partiallyCommittedElem.forceComplete();
1423
- }
1424
- }
1434
+ this.updateSynchronizationVersion({
1435
+ initializeReverseSyncVersion: this._isProvenanceInitTransform,
1436
+ });
1425
1437
  // TODO: ignore if we remove change cache usage
1426
1438
  if (!this._options.noDetachChangeCache) {
1427
1439
  if (core_backend_1.ChangeSummaryManager.isChangeCacheAttached(this.sourceDb))
@@ -1434,35 +1446,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1434
1446
  this.targetDb.codeValueBehavior = "trim-unicode-whitespace";
1435
1447
  }
1436
1448
  /* eslint-enable @itwin/no-internal */
1437
- const defaultSaveTargetChanges = () => this.targetDb.saveChanges();
1438
- await (options?.saveTargetChanges ?? defaultSaveTargetChanges)(this);
1439
- if (this.isReverseSynchronization)
1440
- this.sourceDb.saveChanges();
1441
- const description = `${this._isProvenanceInitTransform
1442
- ? options?.provenanceInitTransformChangesetDescription ??
1443
- `initialized branch provenance with master iModel: ${this.sourceDb.iModelId}`
1444
- : this.isForwardSynchronization
1445
- ? options?.forwardSyncBranchChangesetDescription ??
1446
- `Forward sync of iModel: ${this.sourceDb.iModelId}`
1447
- : options?.reverseSyncMasterChangesetDescription ??
1448
- `Reverse sync of iModel: ${this.sourceDb.iModelId}`}`;
1449
- if (this.targetDb.isBriefcaseDb()) {
1450
- // This relies on authorizationClient on iModelHost being defined, otherwise this will fail
1451
- await this.targetDb.pushChanges({
1452
- description,
1453
- });
1454
- }
1455
- if (this.isReverseSynchronization && this.sourceDb.isBriefcaseDb()) {
1456
- // This relies on authorizationClient on iModelHost being defined, otherwise this will fail
1457
- await this.sourceDb.pushChanges({
1458
- description: options?.reverseSyncBranchChangesetDescription ??
1459
- `Update provenance in response to a reverse sync to iModel: ${this.targetDb.iModelId}`,
1460
- });
1461
- }
1462
1449
  }
1463
1450
  /** Imports all relationships that subclass from the specified base class.
1464
1451
  * @param baseRelClassFullName The specified base relationship class.
1465
- * @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.
1466
1453
  */
1467
1454
  async processRelationships(baseRelClassFullName) {
1468
1455
  await this.initialize();
@@ -1535,8 +1522,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1535
1522
  }
1536
1523
  /** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
1537
1524
  * @deprecated in 1.x. Don't use this anymore
1538
- * @see processChanges
1539
- * @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.
1540
1527
  * @throws [[IModelError]] If the required provenance information is not available to detect deletes.
1541
1528
  */
1542
1529
  async detectRelationshipDeletes() {
@@ -1603,22 +1590,25 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1603
1590
  * This override calls [[onTransformElementAspect]] and then [IModelImporter.importElementUniqueAspect]($transformer) to update the target iModel.
1604
1591
  */
1605
1592
  onExportElementUniqueAspect(sourceAspect) {
1606
- const targetElementId = this.context.findTargetElementId(sourceAspect.element.id);
1607
- const targetAspectProps = this.onTransformElementAspect(sourceAspect, targetElementId);
1608
- this.collectUnmappedReferences(sourceAspect);
1593
+ const targetAspectProps = this.onTransformElementAspect(sourceAspect);
1594
+ if (!this.doAllReferencesExistInTarget(sourceAspect)) {
1595
+ this._partiallyCommittedAspectIds.add(sourceAspect.id);
1596
+ }
1609
1597
  const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
1610
1598
  this.context.remapElementAspect(sourceAspect.id, targetId);
1611
- this.resolvePendingReferences(sourceAspect);
1612
1599
  }
1613
1600
  /** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
1614
1601
  * This override calls [[onTransformElementAspect]] for each ElementMultiAspect and then [IModelImporter.importElementMultiAspects]($transformer) to update the target iModel.
1615
1602
  * @note ElementMultiAspects are handled as a group to make it easier to differentiate between insert, update, and delete.
1616
1603
  */
1617
1604
  onExportElementMultiAspects(sourceAspects) {
1618
- const targetElementId = this.context.findTargetElementId(sourceAspects[0].element.id);
1619
1605
  // Transform source ElementMultiAspects into target ElementAspectProps
1620
- const targetAspectPropsArray = sourceAspects.map((srcA) => this.onTransformElementAspect(srcA, targetElementId));
1621
- 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
+ });
1622
1612
  // const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
1623
1613
  const targetIds = this.importer.importElementMultiAspects(targetAspectPropsArray, (a) => {
1624
1614
  const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
@@ -1628,16 +1618,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1628
1618
  });
1629
1619
  for (let i = 0; i < targetIds.length; ++i) {
1630
1620
  this.context.remapElementAspect(sourceAspects[i].id, targetIds[i]);
1631
- this.resolvePendingReferences(sourceAspects[i]);
1632
1621
  }
1633
1622
  }
1634
1623
  /** Transform the specified sourceElementAspect into ElementAspectProps for the target iModel.
1635
1624
  * @param sourceElementAspect The ElementAspect from the source iModel to be transformed.
1636
- * @param _targetElementId The ElementId of the target Element that will own the ElementAspects after transformation.
1637
1625
  * @returns ElementAspectProps for the target iModel.
1638
1626
  * @note A subclass can override this method to provide custom transform behavior.
1639
1627
  */
1640
- onTransformElementAspect(sourceElementAspect, _targetElementId) {
1628
+ onTransformElementAspect(sourceElementAspect) {
1641
1629
  const targetElementAspectProps = this.context.cloneElementAspect(sourceElementAspect);
1642
1630
  return targetElementAspectProps;
1643
1631
  }
@@ -1663,7 +1651,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1663
1651
  let schemaFileName = schema.name + ext;
1664
1652
  // many file systems have a max file-name/path-segment size of 255, so we workaround that on all systems
1665
1653
  const systemMaxPathSegmentSize = 255;
1666
- if (schemaFileName.length > systemMaxPathSegmentSize) {
1654
+ // windows usually has a limit for the total path length of 260
1655
+ const windowsMaxPathLimit = 260;
1656
+ if (schemaFileName.length > systemMaxPathSegmentSize ||
1657
+ path.join(this._schemaExportDir, schemaFileName).length >=
1658
+ windowsMaxPathLimit) {
1667
1659
  // this name should be well under 255 bytes
1668
1660
  // ( 100 + (Number.MAX_SAFE_INTEGER.toString().length = 16) + (ext.length = 13) ) = 129 which is less than 255
1669
1661
  // You'd have to be past 2**53-1 (Number.MAX_SAFE_INTEGER) long named schemas in order to hit decimal formatting,
@@ -1673,6 +1665,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1673
1665
  nodeAssert(schemaFileName.length <= systemMaxPathSegmentSize, "Schema name was still long. This is a bug.");
1674
1666
  this._longNamedSchemasMap.set(schema.name, schemaFileName);
1675
1667
  }
1668
+ /* eslint-disable-next-line deprecation/deprecation */
1676
1669
  this.sourceDb.nativeDb.exportSchema(schema.name, this._schemaExportDir, schemaFileName);
1677
1670
  return { schemaPath: path.join(this._schemaExportDir, schemaFileName) };
1678
1671
  }
@@ -1713,7 +1706,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1713
1706
  }
1714
1707
  }
1715
1708
  /** Cause all fonts to be exported from the source iModel and imported into the target iModel.
1716
- * @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.
1717
1710
  */
1718
1711
  async processFonts() {
1719
1712
  // we do not need to initialize for this since no entities are exported
@@ -1725,14 +1718,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1725
1718
  this.context.importFont(font.id);
1726
1719
  }
1727
1720
  /** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
1728
- * @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.
1729
1722
  */
1730
1723
  async processCodeSpecs() {
1731
1724
  await this.initialize();
1732
1725
  return this.exporter.exportCodeSpecs();
1733
1726
  }
1734
1727
  /** Cause a single CodeSpec to be exported from the source iModel and imported into the target iModel.
1735
- * @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.
1736
1729
  */
1737
1730
  async processCodeSpec(codeSpecName) {
1738
1731
  await this.initialize();
@@ -1756,21 +1749,23 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1756
1749
  this.context.remapElement(sourceSubjectId, targetSubjectId);
1757
1750
  await this.processChildElements(sourceSubjectId);
1758
1751
  await this.processSubjectSubModels(sourceSubjectId);
1759
- return this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
1752
+ this.completePartiallyCommittedElements();
1753
+ this.completePartiallyCommittedAspects();
1760
1754
  }
1761
1755
  /**
1762
1756
  * Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
1763
- * 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.
1764
1758
  * @note Called by all `process*` functions implicitly.
1765
1759
  * Overriders must call `super.initialize()` first
1766
1760
  */
1767
- async initialize(args) {
1761
+ async initialize() {
1768
1762
  if (this._initialized)
1769
1763
  return;
1770
- await this._tryInitChangesetData(args);
1764
+ this.initScopeProvenance();
1765
+ await this._tryInitChangesetData(this._options.argsForProcessChanges);
1771
1766
  await this.context.initialize();
1772
1767
  // need exporter initialized to do remapdeletedsourceentities.
1773
- await this.exporter.initialize(this.getExportInitOpts(args ?? {}));
1768
+ await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
1774
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).
1775
1770
  await this.processChangesets();
1776
1771
  this._initialized = true;
@@ -1797,6 +1792,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1797
1792
  for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementRefersToElements)")) {
1798
1793
  relationshipECClassIds.add(row.ECInstanceId);
1799
1794
  }
1795
+ const elementECClassIds = new Set();
1796
+ for await (const row of this.sourceDb.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.Element)")) {
1797
+ elementECClassIds.add(row.ECInstanceId);
1798
+ }
1800
1799
  // For later use when processing deletes.
1801
1800
  const alreadyImportedElementInserts = new Set();
1802
1801
  const alreadyImportedModelInserts = new Set();
@@ -1832,10 +1831,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1832
1831
  const changeType = change.$meta?.op;
1833
1832
  if (changeType === "Deleted" &&
1834
1833
  change?.$meta?.classFullName === core_backend_1.ExternalSourceAspect.classFullName &&
1835
- change.Scope.Id === this.targetScopeElementId) {
1834
+ change.Scope.Id === this.targetScopeElementId &&
1835
+ change.Kind === core_backend_1.ExternalSourceAspect.Kind.Element) {
1836
1836
  elemIdToScopeEsa.set(change.Element.Id, change);
1837
1837
  }
1838
- else if (changeType === "Inserted" || changeType === "Updated")
1838
+ else if ((changeType === "Inserted" || changeType === "Updated") &&
1839
+ change.ECClassId !== undefined &&
1840
+ elementECClassIds.has(change.ECClassId))
1839
1841
  hasElementChangedCache.add(change.ECInstanceId);
1840
1842
  }
1841
1843
  // Loop to process deletes.
@@ -1869,7 +1871,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1869
1871
  async processDeletedOp(change, mapOfDeletedElemIdToScopeEsas, isRelationship, alreadyImportedElementInserts, alreadyImportedModelInserts) {
1870
1872
  // we need a connected iModel with changes to remap elements with deletions
1871
1873
  const notConnectedModel = this.sourceDb.iTwinId === undefined;
1872
- const noChanges = this._synchronizationVersion.index === this.sourceDb.changeset.index;
1874
+ const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
1873
1875
  if (notConnectedModel || noChanges)
1874
1876
  return;
1875
1877
  /**
@@ -1966,17 +1968,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1966
1968
  this._sourceChangeDataState = "unconnected";
1967
1969
  return;
1968
1970
  }
1969
- const noChanges = this._synchronizationVersion.index === this.sourceDb.changeset.index;
1971
+ const noChanges = this.synchronizationVersion.index === this.sourceDb.changeset.index;
1970
1972
  if (noChanges) {
1971
1973
  this._sourceChangeDataState = "no-changes";
1972
1974
  this._csFileProps = [];
1973
1975
  return;
1974
1976
  }
1977
+ const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
1975
1978
  // NOTE: that we do NOT download the changesummary for the last transformed version, we want
1976
1979
  // to ignore those already processed changes
1977
- const startChangesetIndexOrId = args.startChangeset?.index ??
1978
- args.startChangeset?.id ??
1979
- this._synchronizationVersion.index + 1;
1980
+ const startChangesetIndexOrId = startChangeset?.index ??
1981
+ startChangeset?.id ??
1982
+ this.synchronizationVersion.index + 1;
1980
1983
  const endChangesetId = this.sourceDb.changeset.id;
1981
1984
  const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
1982
1985
  ? indexOrId
@@ -1985,20 +1988,20 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1985
1988
  iModelId: this.sourceDb.iModelId,
1986
1989
  // eslint-disable-next-line deprecation/deprecation
1987
1990
  changeset: { id: indexOrId },
1988
- accessToken: args.accessToken,
1989
1991
  })
1990
1992
  .then((changeset) => changeset.index)));
1991
- const missingChangesets = startChangesetIndex > this._synchronizationVersion.index + 1;
1992
- if (!this._options.ignoreMissingChangesetsInSynchronizations &&
1993
- startChangesetIndex !== this._synchronizationVersion.index + 1 &&
1994
- this._synchronizationVersion.index !== -1) {
1993
+ const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
1994
+ if (!this._options.argsForProcessChanges
1995
+ ?.ignoreMissingChangesetsInSynchronizations &&
1996
+ startChangesetIndex !== this.synchronizationVersion.index + 1 &&
1997
+ this.synchronizationVersion.index !== -1) {
1995
1998
  throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
1996
1999
  " startChangesetId should be" +
1997
2000
  " exactly the first changeset *after* the previous synchronization to not miss data." +
1998
2001
  ` You specified '${startChangesetIndexOrId}' which is changeset #${startChangesetIndex}` +
1999
- ` but the previous synchronization for this targetScopeElement was '${this._synchronizationVersion.id}'` +
2000
- ` which is changeset #${this._synchronizationVersion.index}. The transformer expected` +
2001
- ` #${this._synchronizationVersion.index + 1}.`);
2002
+ ` but the previous synchronization for this targetScopeElement was '${this.synchronizationVersion.id}'` +
2003
+ ` which is changeset #${this.synchronizationVersion.index}. The transformer expected` +
2004
+ ` #${this.synchronizationVersion.index + 1}.`);
2002
2005
  }
2003
2006
  nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
2004
2007
  const changesetsToSkip = this.isReverseSynchronization
@@ -2020,15 +2023,44 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2020
2023
  csFileProps.push(...fileProps);
2021
2024
  }
2022
2025
  this._csFileProps = csFileProps;
2023
- this._sourceChangeDataState = "has-changes";
2026
+ /** Theres a possibility that our csFileProps length is still 0 here, since we skip cs indices found in the pendingSync and pendingReverseSync indices arrays. */
2027
+ this._sourceChangeDataState =
2028
+ this._csFileProps.length === 0 ? "no-changes" : "has-changes";
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();
2024
2059
  }
2025
2060
  /** Export everything from the source iModel and import the transformed entities into the target iModel.
2026
2061
  * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
2027
2062
  */
2028
- async processAll(options) {
2029
- this.logSettings();
2030
- this.initScopeProvenance();
2031
- await this.initialize();
2063
+ async processAll() {
2032
2064
  await this.exporter.exportCodeSpecs();
2033
2065
  await this.exporter.exportFonts();
2034
2066
  if (this._options.skipPropagateChangesToRootElements) {
@@ -2040,18 +2072,21 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2040
2072
  else {
2041
2073
  await this.exporter.exportModel(core_common_1.IModel.repositoryModelId);
2042
2074
  }
2075
+ this.completePartiallyCommittedElements();
2043
2076
  await this.exporter["exportAllAspects"](); // eslint-disable-line @typescript-eslint/dot-notation
2077
+ this.completePartiallyCommittedAspects();
2044
2078
  await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
2045
- await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
2046
2079
  if (this._options.forceExternalSourceAspectProvenance &&
2047
2080
  this.shouldDetectDeletes()) {
2081
+ // eslint-disable-next-line deprecation/deprecation
2048
2082
  await this.detectElementDeletes();
2083
+ // eslint-disable-next-line deprecation/deprecation
2049
2084
  await this.detectRelationshipDeletes();
2050
2085
  }
2051
2086
  if (this._options.optimizeGeometry)
2052
2087
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2053
2088
  this.importer.computeProjectExtents();
2054
- await this.finalizeTransformation(options);
2089
+ this.finalizeTransformation();
2055
2090
  }
2056
2091
  markLastProvenance(sourceAspect, { isRelationship = false }) {
2057
2092
  this._lastProvenanceEntityInfo =
@@ -2068,42 +2103,45 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2068
2103
  }
2069
2104
  /** Export changes from the source iModel and import the transformed entities into the target iModel.
2070
2105
  * Inserts, updates, and deletes are determined by inspecting the changeset(s).
2071
- * @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
2072
2109
  * @note if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
2073
2110
  * will automatically be determined and used
2074
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.
2075
2112
  */
2076
2113
  async processChanges(options) {
2077
- this._isSynchronization = true;
2078
- this.initScopeProvenance();
2079
- this.logSettings();
2080
- await this.initialize(options);
2081
2114
  // must wait for initialization of synchronization provenance data
2082
2115
  await this.exporter.exportChanges(this.getExportInitOpts(options));
2083
- await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
2116
+ this.completePartiallyCommittedElements();
2117
+ this.completePartiallyCommittedAspects();
2084
2118
  if (this._options.optimizeGeometry)
2085
2119
  this.importer.optimizeGeometry(this._options.optimizeGeometry);
2086
2120
  this.importer.computeProjectExtents();
2087
- await this.finalizeTransformation(options);
2121
+ this.finalizeTransformation();
2122
+ const defaultSaveTargetChanges = () => {
2123
+ this.targetDb.saveChanges();
2124
+ };
2125
+ await (options.saveTargetChanges ?? defaultSaveTargetChanges)(this);
2088
2126
  }
2089
2127
  /** Changeset data must be initialized in order to build correct changeOptions.
2090
2128
  * Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
2091
2129
  */
2092
2130
  getExportInitOpts(opts) {
2093
- if (!this._isSynchronization)
2131
+ if (!this._options.argsForProcessChanges)
2094
2132
  return {};
2133
+ const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
2095
2134
  return {
2096
- skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? false,
2097
- accessToken: opts.accessToken,
2135
+ skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
2098
2136
  ...(this._csFileProps
2099
2137
  ? { csFileProps: this._csFileProps }
2100
2138
  : this._changesetRanges
2101
2139
  ? { changesetRanges: this._changesetRanges }
2102
- : opts.startChangeset
2103
- ? { startChangeset: opts.startChangeset }
2140
+ : startChangeset
2141
+ ? { startChangeset }
2104
2142
  : {
2105
2143
  startChangeset: {
2106
- index: this._synchronizationVersion.index + 1,
2144
+ index: this.synchronizationVersion.index + 1,
2107
2145
  },
2108
2146
  }),
2109
2147
  };
@@ -2182,8 +2220,7 @@ class TemplateModelCloner extends IModelTransformer {
2182
2220
  }
2183
2221
  /** Cloning from a template requires this override of onTransformElement. */
2184
2222
  onTransformElement(sourceElement) {
2185
- // eslint-disable-next-line deprecation/deprecation
2186
- const referenceIds = sourceElement.getReferenceConcreteIds();
2223
+ const referenceIds = sourceElement.getReferenceIds();
2187
2224
  referenceIds.forEach((referenceId) => {
2188
2225
  // TODO: consider going through all definition elements at once and remapping them to themselves
2189
2226
  if (!core_backend_1.EntityReferences.isValid(this.context.findTargetEntityId(referenceId))) {