@itwin/imodel-transformer 2.0.0-dev.11 → 2.0.0-dev.13

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 (38) hide show
  1. package/lib/cjs/BranchProvenanceInitializer.js +3 -3
  2. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  3. package/lib/cjs/DetachedExportElementAspectsStrategy.js +1 -1
  4. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  5. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  6. package/lib/cjs/ElementCascadingDeleter.js +9 -9
  7. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  8. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  9. package/lib/cjs/EntityUnifier.js +5 -11
  10. package/lib/cjs/EntityUnifier.js.map +1 -1
  11. package/lib/cjs/ExportElementAspectsStrategy.d.ts +2 -2
  12. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -1
  13. package/lib/cjs/ExportElementAspectsStrategy.js +1 -1
  14. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  15. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -1
  16. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +15 -14
  17. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  18. package/lib/cjs/IModelExporter.d.ts +12 -12
  19. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  20. package/lib/cjs/IModelExporter.js +25 -25
  21. package/lib/cjs/IModelExporter.js.map +1 -1
  22. package/lib/cjs/IModelImporter.d.ts +19 -19
  23. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  24. package/lib/cjs/IModelImporter.js +59 -57
  25. package/lib/cjs/IModelImporter.js.map +1 -1
  26. package/lib/cjs/IModelTransformer.d.ts +36 -145
  27. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  28. package/lib/cjs/IModelTransformer.js +106 -781
  29. package/lib/cjs/IModelTransformer.js.map +1 -1
  30. package/lib/cjs/ProvenanceManager.d.ts +159 -0
  31. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  32. package/lib/cjs/ProvenanceManager.js +677 -0
  33. package/lib/cjs/ProvenanceManager.js.map +1 -0
  34. package/lib/cjs/SyncTypeResolver.d.ts +34 -0
  35. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  36. package/lib/cjs/SyncTypeResolver.js +84 -0
  37. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  38. package/package.json +15 -15
@@ -21,13 +21,9 @@ const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
21
21
  const IModelCloneContext_1 = require("./IModelCloneContext");
22
22
  const EntityUnifier_1 = require("./EntityUnifier");
23
23
  const Algo_1 = require("./Algo");
24
+ const SyncTypeResolver_1 = require("./SyncTypeResolver");
25
+ const ProvenanceManager_1 = require("./ProvenanceManager");
24
26
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
25
- const nullLastProvenanceEntityInfo = {
26
- entityId: core_bentley_1.Id64.invalid,
27
- aspectId: core_bentley_1.Id64.invalid,
28
- aspectVersion: "",
29
- aspectKind: core_backend_1.ExternalSourceAspect.Kind.Element,
30
- };
31
27
  /**
32
28
  * Apply a function to each Id64 in a supported container type of Id64s.
33
29
  * Currently only supports raw Id64String or RelatedElement-like objects containing an `id` property that is a Id64String,
@@ -86,7 +82,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
86
82
  * @beta
87
83
  */
88
84
  _linearSpatialTransform;
89
- _syncType;
85
+ _syncTypeResolver;
86
+ _provenanceManager;
90
87
  /** The Id of the Element in the **target** iModel that represents the **source** repository as a whole and scopes its [ExternalSourceAspect]($backend) instances. */
91
88
  get targetScopeElementId() {
92
89
  return this._options.targetScopeElementId;
@@ -95,132 +92,15 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
95
92
  _elementsWithExplicitlyTrackedProvenance = new Set();
96
93
  _partiallyCommittedElementIds = new Set();
97
94
  _partiallyCommittedAspectIds = new Set();
98
- /** the options that were used to initialize this transformer */
99
- _options;
100
- /**
101
- * 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.
102
- * 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.
103
- * A couple ofoutdated tests run their first transform providing a source and targetdb that are slightly different which is no longer supported. In order to not remove these tests which are still providing value
104
- * this private property on the IModelTransformer exists.
105
- */
106
- _allowNoScopingESA = false;
107
- static noEsaSyncDirectionErrorMessage = "Couldn't find an external source aspect to determine sync direction. This often means that the master->branch relationship has not been established. Consider running the transformer with wasSourceIModelCopiedToTarget set to true.";
108
- /**
109
- * Queries for an esa which matches the props in the provided aspectProps.
110
- * @param dbToQuery db to run the query on for scope external source
111
- * @param aspectProps aspectProps to search for @see ExternalSourceAspectProps
112
- */
113
- static async queryScopeExternalSourceAspect(dbToQuery, aspectProps) {
114
- const sql = `
115
- SELECT ECInstanceId, Version, JsonProperties
116
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
117
- WHERE Element.Id=:elementId
118
- AND Scope.Id=:scopeId
119
- AND Kind=:kind
120
- AND Identifier=:identifier
121
- LIMIT 1
122
- `;
123
- // if (aspectProps.scope === undefined) return undefined;
124
- // const params = new QueryBinder();
125
- // params.bindId("elementId", aspectProps.element.id);
126
- // params.bindId("scopeId", aspectProps.scope.id);
127
- // params.bindString("kind", aspectProps.kind);
128
- // params.bindString("identifier", aspectProps.identifier);
129
- // const reader = dbToQuery.createQueryReader(sql, params, {usePrimaryConn: true});
130
- // if (await reader.step()) {
131
- // const aspectId = reader.current.id;
132
- // const version = reader.current.version as string | undefined;
133
- // const jsonProperties = reader.current.jsonProperties as string | undefined;
134
- // return { aspectId, version, jsonProperties };
135
- // }
136
- // return undefined;
137
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
138
- return dbToQuery.withPreparedStatement(sql, (statement) => {
139
- statement.bindId("elementId", aspectProps.element.id);
140
- if (aspectProps.scope === undefined)
141
- return undefined; // return instead of binding an invalid id
142
- statement.bindId("scopeId", aspectProps.scope.id);
143
- statement.bindString("kind", aspectProps.kind);
144
- statement.bindString("identifier", aspectProps.identifier);
145
- if (core_bentley_1.DbResult.BE_SQLITE_ROW !== statement.step())
146
- return undefined;
147
- const aspectId = statement.getValue(0).getId();
148
- const versionValue = statement.getValue(1);
149
- const version = versionValue.isNull
150
- ? undefined
151
- : versionValue.getString();
152
- const jsonPropsValue = statement.getValue(2);
153
- const jsonProperties = jsonPropsValue.isNull
154
- ? undefined
155
- : jsonPropsValue.getString();
156
- return { aspectId, version, jsonProperties };
157
- });
158
- }
159
95
  /**
160
- * Determines the sync direction "forward" or "reverse" of a given sourceDb and targetDb by looking for the scoping ESA.
161
- * If the sourceDb's iModelId is found as the identifier of the expected scoping ESA in the targetDb, then it is a forward synchronization.
162
- * If the targetDb's iModelId is found as the identifier of the expected scoping ESA in the sourceDb, then it is a reverse synchronization.
163
- * @throws if no scoping ESA can be found in either the sourceDb or targetDb which describes a master branch relationship between the two databases.
164
- * @returns "forward" or "reverse"
96
+ * Tracks target element IDs that were imported (inserted or updated) during the current
97
+ * transformation pass. Used to prevent deletion of target elements that have been remapped
98
+ * to a new source element in the same pass (e.g., when a source element is deleted and a
99
+ * new one with the same properties is added, causing a remap to the same target).
165
100
  */
166
- static async determineSyncType(sourceDb, targetDb,
167
- /** @see [[IModelTransformOptions.targetScopeElementId]] */
168
- targetScopeElementId) {
169
- const aspectProps = {
170
- id: undefined,
171
- version: undefined,
172
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
173
- element: {
174
- id: targetScopeElementId,
175
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
176
- },
177
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
178
- identifier: sourceDb.iModelId,
179
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
180
- jsonProperties: undefined,
181
- };
182
- /** First check if the targetDb is the branch (branch is the @see provenanceDb) */
183
- const esaPropsFromTargetDb = await this.queryScopeExternalSourceAspect(targetDb, aspectProps);
184
- if (esaPropsFromTargetDb !== undefined) {
185
- return "forward"; // we found an esa assuming targetDb is the provenanceDb/branch so this is a forward sync.
186
- }
187
- // Now check if the sourceDb is the branch
188
- aspectProps.identifier = targetDb.iModelId;
189
- const esaPropsFromSourceDb = await this.queryScopeExternalSourceAspect(sourceDb, aspectProps);
190
- if (esaPropsFromSourceDb !== undefined) {
191
- return "reverse"; // we found an esa assuming sourceDb is the provenanceDb/branch so this is a reverse sync.
192
- }
193
- throw new Error(this.noEsaSyncDirectionErrorMessage);
194
- }
195
- async determineSyncType() {
196
- if (this._isProvenanceInitTransform) {
197
- return "forward";
198
- }
199
- if (!this._options.argsForProcessChanges) {
200
- return "not-sync";
201
- }
202
- try {
203
- return await IModelTransformer.determineSyncType(this.sourceDb, this.targetDb, this.targetScopeElementId);
204
- }
205
- catch (err) {
206
- if (err instanceof Error &&
207
- err.message === IModelTransformer.noEsaSyncDirectionErrorMessage &&
208
- this._allowNoScopingESA) {
209
- return "forward";
210
- }
211
- throw err;
212
- }
213
- }
214
- async getIsReverseSynchronization() {
215
- if (this._syncType === undefined)
216
- this._syncType = await this.determineSyncType();
217
- return this._syncType === "reverse";
218
- }
219
- async getIsForwardSynchronization() {
220
- if (this._syncType === undefined)
221
- this._syncType = await this.determineSyncType();
222
- return this._syncType === "forward";
223
- }
101
+ _targetElementsImportedInCurrentTransform = new Set();
102
+ /** the options that were used to initialize this transformer */
103
+ _options;
224
104
  _changesetRanges = undefined;
225
105
  /**
226
106
  * Set if the transformer is being used to perform the provenance initialization step of a fork initialization.
@@ -229,16 +109,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
229
109
  _isProvenanceInitTransform;
230
110
  /** The element classes that are considered to define provenance in the iModel */
231
111
  static get provenanceElementClasses() {
232
- return [
233
- core_backend_1.FolderLink,
234
- core_backend_1.SynchronizationConfigLink,
235
- core_backend_1.ExternalSource,
236
- core_backend_1.ExternalSourceAttachment,
237
- ];
112
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementClasses;
238
113
  }
239
114
  /** The element aspect classes that are considered to define provenance in the iModel */
240
115
  static get provenanceElementAspectClasses() {
241
- return [core_backend_1.ExternalSourceAspect];
116
+ return ProvenanceManager_1.ProvenanceManager.provenanceElementAspectClasses;
242
117
  }
243
118
  /** Construct a new IModelTransformer
244
119
  * @param source Specifies the source IModelExporter or the source IModelDb that will be used to construct the source IModelExporter.
@@ -297,14 +172,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
297
172
  this.targetDb = this.importer.targetDb;
298
173
  // create the IModelCloneContext, it must be initialized later
299
174
  this.context = new IModelCloneContext_1.IModelCloneContext(this.sourceDb, this.targetDb);
300
- if (this.sourceDb.isBriefcase && this.targetDb.isBriefcase) {
301
- nodeAssert(this.sourceDb.changeset.index !== undefined &&
302
- this.targetDb.changeset.index !== undefined, "database has no changeset index");
303
- this._startingChangesetIndices = {
304
- target: this.targetDb.changeset.index,
305
- source: this.sourceDb.changeset.index,
306
- };
307
- }
308
175
  // this internal is guaranteed stable for just transformer usage
309
176
  /* eslint-disable @itwin/no-internal */
310
177
  if (("codeValueBehavior" in this.sourceDb)) {
@@ -312,6 +179,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
312
179
  this.targetDb.codeValueBehavior = "exact";
313
180
  }
314
181
  /* eslint-enable @itwin/no-internal */
182
+ this._syncTypeResolver = new SyncTypeResolver_1.SyncTypeResolver(this.context, this._options.targetScopeElementId, !!this._isProvenanceInitTransform, !!this._options.argsForProcessChanges);
183
+ this._provenanceManager = new ProvenanceManager_1.ProvenanceManager(this._options.targetScopeElementId, this._options, this._syncTypeResolver);
315
184
  if (this._options.tryAlignGeolocation) {
316
185
  if (this.sourceDb.geographicCoordinateSystem ||
317
186
  this.targetDb.geographicCoordinateSystem) {
@@ -362,79 +231,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
362
231
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
363
232
  core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
364
233
  }
365
- /** Return the IModelDb where IModelTransformer will store its provenance.
366
- * @note This will be [[targetDb]] except when it is a reverse synchronization. In that case it be [[sourceDb]].
367
- */
368
- async getProvenanceDb() {
369
- return (await this.getIsReverseSynchronization())
370
- ? this.sourceDb
371
- : this.targetDb;
372
- }
373
- /** Return the IModelDb where IModelTransformer looks for entities referred to by stored provenance.
374
- * @note This will be [[sourceDb]] except when it is a reverse synchronization. In that case it be [[targetDb]].
375
- */
376
- async getProvenanceSourceDb() {
377
- return (await this.getIsReverseSynchronization())
378
- ? this.targetDb
379
- : this.sourceDb;
380
- }
381
- /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
382
- static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
383
- const elementId = args.isReverseSynchronization
384
- ? sourceElementId
385
- : targetElementId;
386
- const version = args.isReverseSynchronization
387
- ? args.targetDb.elements.queryLastModifiedTime(targetElementId)
388
- : args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
389
- const aspectIdentifier = args.isReverseSynchronization
390
- ? targetElementId
391
- : sourceElementId;
392
- const aspectProps = {
393
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
394
- element: {
395
- id: elementId,
396
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
397
- },
398
- scope: { id: args.targetScopeElementId },
399
- identifier: aspectIdentifier,
400
- kind: core_backend_1.ExternalSourceAspect.Kind.Element,
401
- version,
402
- };
403
- return aspectProps;
404
- }
405
- static async initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
406
- const provenanceDb = args.isReverseSynchronization
407
- ? args.sourceDb
408
- : args.targetDb;
409
- const aspectIdentifier = args.isReverseSynchronization
410
- ? targetRelInstanceId
411
- : sourceRelInstanceId;
412
- const provenanceRelInstanceId = args.isReverseSynchronization
413
- ? sourceRelInstanceId
414
- : targetRelInstanceId;
415
- const sql = "SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?";
416
- const params = new core_common_1.QueryBinder().bindId(1, provenanceRelInstanceId);
417
- const reader = provenanceDb.createQueryReader(sql, params, {
418
- usePrimaryConn: true,
419
- });
420
- nodeAssert(await reader.step(), "relationship provenance query returned no rows");
421
- const elementId = reader.current[0];
422
- const jsonProperties = args.forceOldRelationshipProvenanceMethod
423
- ? { targetRelInstanceId }
424
- : { provenanceRelInstanceId };
425
- const aspectProps = {
426
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
427
- element: {
428
- id: elementId,
429
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
430
- },
431
- scope: { id: args.targetScopeElementId },
432
- identifier: aspectIdentifier,
433
- kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
434
- jsonProperties: JSON.stringify(jsonProperties),
435
- };
436
- return aspectProps;
437
- }
438
234
  /**
439
235
  * Previously the transformer would insert provenance always pointing to the "target" relationship.
440
236
  * It should (and now by default does) instead insert provenance pointing to the provenanceSource
@@ -444,42 +240,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
444
240
  _forceOldRelationshipProvenanceMethod = false;
445
241
  /** Create an ExternalSourceAspectProps in a standard way for an Element in an iModel --> iModel transformation. */
446
242
  async initElementProvenance(sourceElementId, targetElementId) {
447
- return IModelTransformer.initElementProvenanceOptions(sourceElementId, targetElementId, {
448
- isReverseSynchronization: await this.getIsReverseSynchronization(),
449
- targetScopeElementId: this.targetScopeElementId,
450
- sourceDb: this.sourceDb,
451
- targetDb: this.targetDb,
452
- });
453
- }
454
- /** Create an ExternalSourceAspectProps in a standard way for a Relationship in an iModel --> iModel transformations.
455
- * The ExternalSourceAspect is meant to be owned by the Element in the target iModel that is the `sourceId` of transformed relationship.
456
- * The `identifier` property of the ExternalSourceAspect will be the ECInstanceId of the relationship in the master iModel.
457
- * The ECInstanceId of the relationship in the branch iModel will be stored in the JsonProperties of the ExternalSourceAspect.
458
- */
459
- async initRelationshipProvenance(sourceRelationship, targetRelInstanceId) {
460
- return IModelTransformer.initRelationshipProvenanceOptions(sourceRelationship.id, targetRelInstanceId, {
461
- sourceDb: this.sourceDb,
462
- targetDb: this.targetDb,
463
- isReverseSynchronization: await this.getIsReverseSynchronization(),
464
- targetScopeElementId: this.targetScopeElementId,
465
- forceOldRelationshipProvenanceMethod: this._forceOldRelationshipProvenanceMethod,
466
- });
467
- }
468
- /** NOTE: the json properties must be converted to string before insertion */
469
- _targetScopeProvenanceProps = undefined;
470
- /**
471
- * Index of the changeset that the transformer was at when the transformation begins (was constructed).
472
- * Used to determine at the end which changesets were part of a synchronization.
473
- */
474
- _startingChangesetIndices = undefined;
475
- _cachedSynchronizationVersion = undefined;
476
- /**
477
- * We cache the synchronization version to avoid querying the target scoping ESA multiple times.
478
- * If the target scoping ESA is ever updated we need to clear any potentially cached sync version otherwise we will get stale values.
479
- * Sets this._cachedSynchronizationVersion to undefined.
480
- */
481
- clearCachedSynchronizationVersion() {
482
- this._cachedSynchronizationVersion = undefined;
243
+ return this._provenanceManager.initElementProvenance(sourceElementId, targetElementId);
483
244
  }
484
245
  /** the changeset in the scoping element's source version found for this transformation
485
246
  * @note the version depends on whether this is a reverse synchronization or not, as
@@ -489,394 +250,58 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
489
250
  * @throws if the version is not found in a preexisting scope aspect and @see [[IModelTransformOptions.branchRelationshipDataBehavior]] !== "unsafe-migrate"
490
251
  */
491
252
  async getSynchronizationVersion() {
492
- if (this._cachedSynchronizationVersion === undefined) {
493
- const provenanceScopeAspect = await this.tryGetProvenanceScopeAspect();
494
- if (!provenanceScopeAspect) {
495
- return { index: -1, id: "" }; // first synchronization.
496
- }
497
- const version = (await this.getIsReverseSynchronization())
498
- ? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
499
- : provenanceScopeAspect.version;
500
- if (!version &&
501
- this._options.branchRelationshipDataBehavior === "unsafe-migrate") {
502
- return { index: -1, id: "" }; // previous synchronization was done before fed guid update.
503
- }
504
- if (version === undefined) {
505
- 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.
506
- Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`);
507
- }
508
- const [id, index] = version === "" ? ["", -1] : version.split(";");
509
- if (Number.isNaN(Number(index)))
510
- throw new Error("Could not parse version data from scope aspect");
511
- this._cachedSynchronizationVersion = { index: Number(index), id }; // synchronization version found and cached.
512
- }
513
- return this._cachedSynchronizationVersion;
253
+ return this._provenanceManager.getSynchronizationVersion();
514
254
  }
515
255
  /**
516
256
  * @returns provenance scope aspect if it exists in the provenanceDb.
517
257
  * Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
518
258
  */
519
259
  async tryGetProvenanceScopeAspect() {
520
- const scopeProvenanceAspectProps = await IModelTransformer.queryScopeExternalSourceAspect(await this.getProvenanceDb(), {
521
- id: undefined,
522
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
523
- scope: { id: core_common_1.IModel.rootSubjectId },
524
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
525
- element: { id: this.targetScopeElementId ?? core_common_1.IModel.rootSubjectId },
526
- identifier: (await this.getProvenanceSourceDb()).iModelId,
527
- });
528
- return scopeProvenanceAspectProps !== undefined
529
- ? (await this.getProvenanceDb()).elements.getAspect(scopeProvenanceAspectProps.aspectId)
530
- : undefined;
260
+ return this._provenanceManager.tryGetProvenanceScopeAspect();
531
261
  }
532
262
  /**
533
263
  * Make sure there are no conflicting other scope-type external source aspects on the *target scope element*,
534
264
  * If there are none at all, insert one, then this must be a first synchronization.
535
- * @returns the last synced version (changesetId) on the target scope's external source aspect,
536
- * if this was a [BriefcaseDb]($backend)
537
265
  */
538
266
  async initScopeProvenance() {
539
- const provenanceDb = await this.getProvenanceDb();
540
- const sourceProvenanceDb = await this.getProvenanceSourceDb();
541
- const aspectProps = {
542
- id: undefined,
543
- version: undefined,
544
- classFullName: core_backend_1.ExternalSourceAspect.classFullName,
545
- element: {
546
- id: this.targetScopeElementId,
547
- relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
548
- },
549
- scope: { id: core_common_1.IModel.rootSubjectId }, // the root Subject scopes scope elements
550
- identifier: sourceProvenanceDb.iModelId,
551
- kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
552
- jsonProperties: undefined,
553
- };
554
- const foundEsaProps = await IModelTransformer.queryScopeExternalSourceAspect(provenanceDb, aspectProps); // this query includes "identifier"
555
- if (foundEsaProps === undefined) {
556
- aspectProps.version = ""; // empty since never before transformed. Will be updated in [[finalizeTransformation]]
557
- aspectProps.jsonProperties = {
558
- pendingReverseSyncChangesetIndices: [],
559
- pendingSyncChangesetIndices: [],
560
- reverseSyncVersion: "", // empty since never before transformed. Will be updated in first reverse sync
561
- };
562
- // this query does not include "identifier" to find possible conflicts
563
- const sql = `
564
- SELECT ECInstanceId
565
- FROM ${core_backend_1.ExternalSourceAspect.classFullName}
566
- WHERE Element.Id=:elementId
567
- AND Scope.Id=:scopeId
568
- AND Kind=:kind
569
- LIMIT 1
570
- `;
571
- const params = new core_common_1.QueryBinder();
572
- params.bindId("elementId", aspectProps.element.id);
573
- params.bindId("scopeId", aspectProps.scope.id); // this scope.id can never be invalid, we create it above
574
- params.bindString("kind", aspectProps.kind);
575
- const reader = provenanceDb.createQueryReader(sql, params, {
576
- usePrimaryConn: true,
577
- });
578
- const hasConflictingScope = await reader.step();
579
- if (hasConflictingScope) {
580
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Provenance scope conflict");
581
- }
582
- if (!this._options.noProvenance) {
583
- const id = provenanceDb.elements.insertAspect({
584
- ...aspectProps,
585
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
586
- });
587
- aspectProps.id = id;
588
- // Busting a potential cached version
589
- this.clearCachedSynchronizationVersion();
590
- }
591
- }
592
- else {
593
- // foundEsaProps is defined.
594
- aspectProps.id = foundEsaProps.aspectId;
595
- aspectProps.version = foundEsaProps.version;
596
- aspectProps.jsonProperties = foundEsaProps.jsonProperties
597
- ? JSON.parse(foundEsaProps.jsonProperties)
598
- : undefined;
599
- // Clone oldProps incase they're changed for logging purposes
600
- const oldProps = JSON.parse(JSON.stringify(aspectProps));
601
- if (this.handleUnsafeMigrate(aspectProps)) {
602
- 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 });
603
- provenanceDb.elements.updateAspect({
604
- ...aspectProps,
605
- jsonProperties: JSON.stringify(aspectProps.jsonProperties),
606
- });
607
- // Busting a potential cached version
608
- this.clearCachedSynchronizationVersion();
609
- }
610
- }
611
- this._targetScopeProvenanceProps =
612
- aspectProps;
613
- }
614
- /** Returns true if a change was made to the aspectProps. */
615
- handleUnsafeMigrate(aspectProps) {
616
- let madeChange = false;
617
- if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
618
- return madeChange;
619
- const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
620
- const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
621
- "";
622
- if (aspectProps.version === undefined ||
623
- (aspectProps.version === "" &&
624
- aspectProps.version !== fallbackSyncVersionToUse)) {
625
- aspectProps.version = fallbackSyncVersionToUse;
626
- madeChange = true;
627
- }
628
- if (aspectProps.jsonProperties === undefined) {
629
- aspectProps.jsonProperties = {
630
- pendingReverseSyncChangesetIndices: [],
631
- pendingSyncChangesetIndices: [],
632
- reverseSyncVersion: fallbackReverseSyncVersionToUse,
633
- };
634
- madeChange = true;
635
- }
636
- else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
637
- (aspectProps.jsonProperties.reverseSyncVersion === "" &&
638
- aspectProps.jsonProperties.reverseSyncVersion !==
639
- fallbackReverseSyncVersionToUse)) {
640
- aspectProps.jsonProperties.reverseSyncVersion =
641
- fallbackReverseSyncVersionToUse;
642
- madeChange = true;
643
- }
644
- /**
645
- * This case will only be hit when:
646
- * - first transformation was performed on pre-fedguid transformer.
647
- * - a second processAll transformation was performed on the same target-source iModels post-fedguid transformer.
648
- * - change processing was invoked on for the second 'initial' transformation.
649
- * NOTE: This case likely does not exist anymore, but we will keep it just to be sure.
650
- */
651
- if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
652
- undefined) {
653
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
654
- aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
655
- madeChange = true;
656
- }
657
- if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
658
- core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
659
- aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
660
- madeChange = true;
661
- }
662
- return madeChange;
267
+ return this._provenanceManager.initScopeProvenance();
663
268
  }
664
269
  /**
665
- * Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
666
- * and call a function for each one.
667
- * @note provenance is done by federation guids where possible
668
- * @note this may execute on each element more than once! Only use in cases where that is handled
270
+ * Get the IModelDb where provenance (ExternalSourceAspects) is stored.
271
+ * This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
669
272
  */
670
- static async forEachTrackedElement(args) {
671
- if (args.provenanceDb === args.provenanceSourceDb)
672
- return;
673
- if (!args.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
674
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadSchema, "The BisCore schema version of the target database is too old");
675
- }
676
- const sourceDb = args.isReverseSynchronization
677
- ? args.provenanceDb
678
- : args.provenanceSourceDb;
679
- const targetDb = args.isReverseSynchronization
680
- ? args.provenanceSourceDb
681
- : args.provenanceDb;
682
- // query for provenanceDb
683
- const elementIdByFedGuidQuery = `
684
- SELECT e.ECInstanceId, FederationGuid
685
- FROM bis.Element e
686
- ${args.skipPropagateChangesToRootElements
687
- ? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
688
- : ""}
689
- ORDER BY FederationGuid
690
- `;
691
- // iterate through sorted list of fed guids from both dbs to get the intersection
692
- // NOTE: if we exposed the native attach database support,
693
- // we could get the intersection of fed guids in one query, not sure if it would be faster
694
- // OR we could do a raw sqlite query...
695
- const sourceReader = sourceDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
696
- const targetReader = targetDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
697
- let hasSourceRow = await sourceReader.step();
698
- let hasTargetRow = await targetReader.step();
699
- while (hasSourceRow && hasTargetRow) {
700
- const sourceFedGuid = sourceReader.current.federationGuid;
701
- const targetFedGuid = targetReader.current.federationGuid;
702
- if (sourceFedGuid !== undefined &&
703
- targetFedGuid !== undefined &&
704
- sourceFedGuid === targetFedGuid) {
705
- // data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
706
- args.fn(sourceReader.current.id, targetReader.current.id);
707
- }
708
- if (targetFedGuid === undefined ||
709
- (sourceFedGuid !== undefined && sourceFedGuid >= targetFedGuid)) {
710
- hasTargetRow = await targetReader.step();
711
- }
712
- if (sourceFedGuid === undefined ||
713
- (targetFedGuid !== undefined && sourceFedGuid <= targetFedGuid)) {
714
- hasSourceRow = await sourceReader.step();
715
- }
716
- }
717
- // query for provenanceDb
718
- const provenanceAspectsQuery = `
719
- SELECT esa.Identifier, Element.Id
720
- FROM bis.ExternalSourceAspect esa
721
- WHERE Scope.Id=:scopeId
722
- AND Kind=:kind
723
- `;
724
- // Technically this will a second time call the function (as documented) on
725
- // victims of the old provenance method that have both fedguids and an inserted aspect.
726
- // But this is a private function with one known caller where that doesn't matter
727
- const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
728
- ? args.fn(sourceId, targetId)
729
- : args.fn(targetId, sourceId);
730
- const params = new core_common_1.QueryBinder();
731
- params.bindId("scopeId", args.targetScopeElementId);
732
- params.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
733
- const provenanceReader = args.provenanceDb.createQueryReader(provenanceAspectsQuery, params, { usePrimaryConn: true });
734
- for await (const row of provenanceReader) {
735
- // ExternalSourceAspect.Identifier is of type string
736
- const aspectIdentifier = row[0];
737
- const elementId = row.id;
738
- runFnInDataFlowDirection(elementId, aspectIdentifier);
739
- }
740
- }
741
- async forEachTrackedElement(fn) {
742
- return IModelTransformer.forEachTrackedElement({
743
- provenanceSourceDb: await this.getProvenanceSourceDb(),
744
- provenanceDb: await this.getProvenanceDb(),
745
- targetScopeElementId: this.targetScopeElementId,
746
- isReverseSynchronization: await this.getIsReverseSynchronization(),
747
- fn,
748
- skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements ?? true,
749
- });
273
+ async getProvenanceDb() {
274
+ return (await this.getIsReverseSynchronization())
275
+ ? this.sourceDb
276
+ : this.targetDb;
750
277
  }
751
278
  /**
752
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
753
- * The identifier on the ESA is the id of the element in the [[IModelTransformer.provenanceSourceDb]]
754
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
755
- * @param entityInProvenanceSourceId
756
- * @returns the elementId that the ESA is stored on, esa.Element.Id
279
+ * Get whether this is a reverse synchronization.
757
280
  */
758
- async _queryProvenanceForElement(entityInProvenanceSourceId) {
759
- const sql = `
760
- SELECT esa.Element.Id
761
- FROM Bis.ExternalSourceAspect esa
762
- WHERE esa.Kind=?
763
- AND esa.Scope.Id=?
764
- AND esa.Identifier=?
765
- `;
766
- const params = new core_common_1.QueryBinder();
767
- params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Element);
768
- params.bindId(2, this.targetScopeElementId);
769
- params.bindString(3, entityInProvenanceSourceId);
770
- const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
771
- usePrimaryConn: true,
772
- });
773
- if (await result.step()) {
774
- return result.current.id;
775
- }
776
- else
777
- return undefined;
281
+ async getIsReverseSynchronization() {
282
+ return (await this._syncTypeResolver.getSyncType()) === "reverse";
778
283
  }
779
284
  /**
780
- * Queries the provenanceDb for an ESA whose identifier is equal to the provided 'entityInProvenanceSourceId'.
781
- * The identifier on the ESA is the id of the relationship in the [[IModelTransformer.provenanceSourceDb]]
782
- * Therefore it only makes sense to call this function when you have an id in the provenanceSourceDb.
783
- * @param entityInProvenanceSourceId
784
- * @returns
285
+ * Get whether this is a forward synchronization.
785
286
  */
786
- async _queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
787
- const sql = `
788
- SELECT
789
- ECInstanceId,
790
- JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId') AS provenanceRelInstId
791
- FROM Bis.ExternalSourceAspect
792
- WHERE Kind=?
793
- AND Scope.Id=?
794
- AND Identifier=?
795
- `;
796
- const params = new core_common_1.QueryBinder();
797
- params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
798
- params.bindId(2, this.targetScopeElementId);
799
- params.bindString(3, entityInProvenanceSourceId);
800
- const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
801
- usePrimaryConn: true,
802
- });
803
- if (await result.step()) {
804
- const aspectId = result.current.id;
805
- const provenanceRelInstId = result.current.provenanceRelInstId;
806
- const provenanceRelInstanceId = provenanceRelInstId !== undefined
807
- ? provenanceRelInstId
808
- : await this._queryTargetRelId(sourceRelInfo);
809
- return {
810
- aspectId,
811
- relationshipId: provenanceRelInstanceId,
812
- };
813
- }
814
- else
815
- return undefined;
287
+ async getIsForwardSynchronization() {
288
+ return (await this._syncTypeResolver.getSyncType()) === "forward";
816
289
  }
817
- async _queryTargetRelId(sourceRelInfo) {
818
- const targetRelInfo = {
819
- sourceId: this.context.findTargetElementId(sourceRelInfo.sourceId),
820
- targetId: this.context.findTargetElementId(sourceRelInfo.targetId),
821
- };
822
- if (targetRelInfo.sourceId === undefined ||
823
- targetRelInfo.targetId === undefined)
824
- return undefined; // couldn't find an element, rel is invalid or deleted
825
- const sql = `
826
- select ecinstanceid
827
- from bis.elementreferstoelements
828
- where sourceecinstanceid=?
829
- and targetecinstanceid=?
830
- and ecclassid=?
831
- `;
832
- const params = new core_common_1.QueryBinder();
833
- params.bindId(1, targetRelInfo.sourceId);
834
- params.bindId(2, targetRelInfo.targetId);
835
- params.bindId(3, await this._targetClassNameToClassId(sourceRelInfo.classFullName));
836
- const result = this.targetDb.createQueryReader(sql, params, {
837
- usePrimaryConn: true,
290
+ /**
291
+ * Updates the synchronization version on the scope ESA.
292
+ */
293
+ async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
294
+ return this._provenanceManager.updateSynchronizationVersion({
295
+ initializeReverseSyncVersion,
296
+ sourceChangeDataState: this._sourceChangeDataState,
838
297
  });
839
- if (await result.step())
840
- return result.current.id;
841
- else
842
- return undefined;
843
- }
844
- _targetClassNameToClassIdCache = new Map();
845
- async _targetClassNameToClassId(classFullName) {
846
- let classId = this._targetClassNameToClassIdCache.get(classFullName);
847
- if (classId === undefined) {
848
- classId = await this._getRelClassId(this.targetDb, classFullName);
849
- this._targetClassNameToClassIdCache.set(classFullName, classId);
850
- }
851
- return classId;
852
- }
853
- // NOTE: this doesn't handle remapped element classes,
854
- // but is only used for relationships rn
855
- async _getRelClassId(db, classFullName) {
856
- const sql = `
857
- SELECT c.ECInstanceId
858
- FROM ECDbMeta.ECClassDef c
859
- JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
860
- WHERE s.Name=? AND c.Name=?
861
- `;
862
- const params = new core_common_1.QueryBinder();
863
- const [schemaName, className] = classFullName.indexOf(".") !== -1
864
- ? classFullName.split(".")
865
- : classFullName.split(":");
866
- params.bindString(1, schemaName);
867
- params.bindString(2, className);
868
- const result = db.createQueryReader(sql, params, { usePrimaryConn: true });
869
- if (await result.step())
870
- return result.current.id;
871
- (0, core_bentley_1.assert)(false, "relationship was not found");
872
298
  }
873
299
  /** Returns `true` if *brute force* delete detections should be run.
874
300
  * @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
875
301
  * @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
876
302
  */
877
- shouldDetectDeletes() {
878
- nodeAssert(this._syncType !== undefined);
879
- return this._syncType === "not-sync";
303
+ async shouldDetectDeletes() {
304
+ return (await this._syncTypeResolver.getSyncType()) === "not-sync";
880
305
  }
881
306
  /** Transform the specified sourceElement into ElementProps for the target iModel.
882
307
  * @param sourceElement The Element from the source iModel to transform.
@@ -1087,7 +512,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1087
512
  /** Override of [IModelExportHandler.shouldExportElement]($transformer) that is called to determine if an element should be exported from the source iModel.
1088
513
  * @note Reaching this point means that the element has passed the standard exclusion checks in IModelExporter.
1089
514
  */
1090
- shouldExportElement(_sourceElement) {
515
+ async shouldExportElement(_sourceElement) {
1091
516
  return true;
1092
517
  }
1093
518
  /**
@@ -1154,6 +579,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1154
579
  (!isElemInTarget || !(await dbHasModel(this.targetDb, idOfElemInTarget)));
1155
580
  return { needsElemImport: !isElemInTarget, needsModelImport };
1156
581
  }
582
+ // In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
583
+ // https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
584
+ // Code class constructor trims white spaces from code value.
585
+ // Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
586
+ // It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
587
+ async queryElementIdByCode(iModel, code) {
588
+ if (core_bentley_1.Id64.isInvalid(code.spec))
589
+ throw new Error("Invalid CodeSpec");
590
+ if (code.value === undefined)
591
+ throw new Error("Invalid Code");
592
+ const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
593
+ const queryBinder = new core_common_1.QueryBinder()
594
+ .bindId(1, code.spec)
595
+ .bindId(2, core_bentley_1.Id64.fromString(code.scope))
596
+ .bindString(3, code.value);
597
+ const queryReader = iModel.createQueryReader(query, queryBinder, {
598
+ usePrimaryConn: true,
599
+ });
600
+ return (await queryReader.step()) ? queryReader.current[0] : undefined;
601
+ }
1157
602
  /** Override of [IModelExportHandler.onExportElement]($transformer) that imports an element into the target iModel when it is exported from the source iModel.
1158
603
  * This override calls [[onTransformElement]] and then [IModelImporter.importElement]($transformer) to update the target iModel.
1159
604
  */
@@ -1232,11 +677,12 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1232
677
  }
1233
678
  }
1234
679
  if (!this._options.wasSourceIModelCopiedToTarget) {
1235
- this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
680
+ await this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
1236
681
  }
1237
682
  if (targetElementProps.id === undefined) {
1238
683
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "targetElementProps.id should be assigned by importElement");
1239
684
  }
685
+ this._targetElementsImportedInCurrentTransform.add(targetElementProps.id);
1240
686
  this.context.remapElement(sourceElement.id, targetElementProps.id);
1241
687
  // the transformer does not currently 'split' or 'join' any elements, therefore, it does not
1242
688
  // insert external source aspects because federation guids are sufficient for this.
@@ -1247,13 +693,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1247
693
  // FIXME: make public and improve `initElementProvenance` API for usage by consolidators
1248
694
  const provenanceDb = await this.getProvenanceDb();
1249
695
  if (!this._options.noProvenance) {
1250
- let provenance = this._options.forceExternalSourceAspectProvenance ||
696
+ const provenance = this._options.forceExternalSourceAspectProvenance ||
1251
697
  this._elementsWithExplicitlyTrackedProvenance.has(sourceElement.id)
1252
698
  ? undefined
1253
699
  : sourceElement.federationGuid;
1254
700
  if (!provenance) {
1255
701
  const aspectProps = await this.initElementProvenance(sourceElement.id, targetElementProps.id);
1256
- const foundEsaProps = await IModelTransformer.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
702
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1257
703
  if (foundEsaProps === undefined)
1258
704
  aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
1259
705
  else {
@@ -1261,44 +707,26 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1261
707
  aspectProps.id = foundEsaProps.aspectId;
1262
708
  provenanceDb.elements.updateAspect(aspectProps);
1263
709
  }
1264
- provenance = aspectProps;
1265
710
  }
1266
- this.markLastProvenance(provenance, { isRelationship: false });
1267
711
  }
1268
712
  }
1269
- // In iTwin js 5.x Elements.queryElementIdByCode() uses Code class to query id:
1270
- // https://github.com/iTwin/itwinjs-core/blob/master/core/backend/src/IModelDb.ts#L2779
1271
- // Code class constructor trims white spaces from code value.
1272
- // Custom implementation of queryElementIdByCode() was added to support querying elements with code values that have trailing whitespaces.
1273
- // It mimicks 4.x implementation: https://github.com/iTwin/itwinjs-core/blob/9c8b394ec3878a39764be81f928fd8b0b9115d31/core/backend/src/IModelDb.ts#L1882
1274
- async queryElementIdByCode(iModel, code) {
1275
- if (core_bentley_1.Id64.isInvalid(code.spec))
1276
- throw new Error("Invalid CodeSpec");
1277
- if (code.value === undefined)
1278
- throw new Error("Invalid Code");
1279
- const query = "SELECT ECInstanceId FROM BisCore:Element WHERE CodeSpec.Id=? AND CodeScope.Id=? AND CodeValue=?";
1280
- const queryBinder = new core_common_1.QueryBinder()
1281
- .bindId(1, code.spec)
1282
- .bindId(2, core_bentley_1.Id64.fromString(code.scope))
1283
- .bindString(3, code.value);
1284
- const queryReader = iModel.createQueryReader(query, queryBinder, {
1285
- usePrimaryConn: true,
1286
- });
1287
- return (await queryReader.step()) ? queryReader.current[0] : undefined;
1288
- }
1289
713
  /** Override of [IModelExportHandler.onDeleteElement]($transformer) that is called when [IModelExporter]($transformer) detects that an Element has been deleted from the source iModel.
1290
714
  * This override propagates the delete to the target iModel via [IModelImporter.deleteElement]($transformer).
1291
715
  */
1292
- onDeleteElement(sourceElementId) {
716
+ async onDeleteElement(sourceElementId) {
1293
717
  const targetElementId = this.context.findTargetElementId(sourceElementId);
1294
718
  if (core_bentley_1.Id64.isValidId64(targetElementId)) {
1295
- this.importer.deleteElement(targetElementId);
719
+ // Skip deletion if this target element was already imported (inserted/updated) during
720
+ // this transformation pass.
721
+ if (!this._targetElementsImportedInCurrentTransform.has(targetElementId)) {
722
+ await this.importer.deleteElement(targetElementId);
723
+ }
1296
724
  }
1297
725
  }
1298
726
  /** Override of [IModelExportHandler.onExportModel]($transformer) that is called when a Model should be exported from the source iModel.
1299
727
  * This override calls [[onTransformModel]] and then [IModelImporter.importModel]($transformer) to update the target iModel.
1300
728
  */
1301
- onExportModel(sourceModel) {
729
+ async onExportModel(sourceModel) {
1302
730
  if (this._options.skipPropagateChangesToRootElements &&
1303
731
  core_common_1.IModel.repositoryModelId === sourceModel.id)
1304
732
  return; // The RepositoryModel should not be directly imported
@@ -1309,7 +737,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1309
737
  if (isRemappedRootSubject)
1310
738
  return;
1311
739
  const targetModelProps = this.onTransformModel(sourceModel, targetModeledElementId);
1312
- this.importer.importModel(targetModelProps);
740
+ await this.importer.importModel(targetModelProps);
1313
741
  }
1314
742
  /** Override of [IModelExportHandler.onDeleteModel]($transformer) that is called when [IModelExporter]($transformer) detects that a [Model]($backend) has been deleted from the source iModel. */
1315
743
  async onDeleteModel(sourceModelId) {
@@ -1428,95 +856,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1428
856
  targetModelProps.parentModel = this.context.findTargetElementId(targetModelProps.parentModel);
1429
857
  return targetModelProps;
1430
858
  }
1431
- /**
1432
- * Called at the end of a transformation,
1433
- * updates the target scope element to say that transformation up through the
1434
- * source's changeset has been performed. Also stores all changesets that occurred
1435
- * during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
1436
- *
1437
- * You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
1438
- * It is public for unsupported use cases of custom synchronization transforms.
1439
- * @note If [[IModelTransformOptions.argsForProcessChanges]] is not defined in this transformation, this function will return early without updating the sync version,
1440
- * unless the `initializeReverseSyncVersion` option is set to `true`
1441
- *
1442
- * The `initializeReverseSyncVersion` is added to set the reverse synchronization version during a forward synchronization.
1443
- * 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.
1444
- * 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.
1445
- *
1446
- * Note that typically, the reverseSyncVersion is saved as the last changeset merged from the branch into master.
1447
- * 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.
1448
- */
1449
- async updateSynchronizationVersion({ initializeReverseSyncVersion = false, } = {}) {
1450
- const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion &&
1451
- this._sourceChangeDataState !== "has-changes";
1452
- if (shouldSkipSyncVersionUpdate)
1453
- return;
1454
- nodeAssert(this._targetScopeProvenanceProps);
1455
- const sourceVersion = `${this.sourceDb.changeset.id};${this.sourceDb.changeset.index}`;
1456
- const targetVersion = `${this.targetDb.changeset.id};${this.targetDb.changeset.index}`;
1457
- if (await this.getIsReverseSynchronization()) {
1458
- const oldVersion = this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion;
1459
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
1460
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1461
- sourceVersion;
1462
- }
1463
- else {
1464
- core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
1465
- this._targetScopeProvenanceProps.version = sourceVersion;
1466
- // save reverse sync version
1467
- if (initializeReverseSyncVersion) {
1468
- core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
1469
- this._targetScopeProvenanceProps.jsonProperties.reverseSyncVersion =
1470
- targetVersion;
1471
- }
1472
- }
1473
- if (this._options.argsForProcessChanges ||
1474
- (this._startingChangesetIndices && initializeReverseSyncVersion)) {
1475
- nodeAssert(this.targetDb.changeset.index !== undefined &&
1476
- this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
1477
- // Store in a local variable, so typescript knows it's defined (due to the assert above)
1478
- const startingChangesetIndices = this._startingChangesetIndices;
1479
- const jsonProps = this._targetScopeProvenanceProps.jsonProperties;
1480
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1481
- core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1482
- const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
1483
- const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
1484
- // Determine which keys to clear and update based on the synchronization direction
1485
- let syncChangesetsToClearKey;
1486
- let syncChangesetsToUpdateKey;
1487
- if (await this.getIsReverseSynchronization()) {
1488
- syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
1489
- syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
1490
- }
1491
- else {
1492
- syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
1493
- syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
1494
- }
1495
- // NOTE that as documented in [[processChanges]], this assumes that right after
1496
- // transformation finalization, the work will be saved immediately, otherwise we've
1497
- // just marked this changeset as a synchronization to ignore, and the user can add other
1498
- // stuff to it which would break future synchronizations
1499
- for (let i = startingChangesetIndices.target + 1; i <= this.targetDb.changeset.index + 1; i++)
1500
- jsonProps[syncChangesetsToUpdateKey].push(i);
1501
- // Only keep the changeset indices which are greater than the source, this means they haven't been processed yet.
1502
- jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
1503
- return csIndex > startingChangesetIndices.source;
1504
- });
1505
- // if reverse sync then we may have received provenance changes which should be marked as sync changes
1506
- if (await this.getIsReverseSynchronization()) {
1507
- nodeAssert(this.sourceDb.changeset.index !== undefined, "changeset didn't exist");
1508
- for (let i = startingChangesetIndices.source + 1; i <= this.sourceDb.changeset.index + 1; i++)
1509
- jsonProps.pendingReverseSyncChangesetIndices.push(i);
1510
- }
1511
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${jsonProps.pendingReverseSyncChangesetIndices}`);
1512
- core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${jsonProps.pendingSyncChangesetIndices}`);
1513
- }
1514
- (await this.getProvenanceDb()).elements.updateAspect({
1515
- ...this._targetScopeProvenanceProps,
1516
- jsonProperties: JSON.stringify(this._targetScopeProvenanceProps.jsonProperties),
1517
- });
1518
- this.clearCachedSynchronizationVersion();
1519
- }
1520
859
  // FIXME<MIKE>: is this necessary when manually using low level transform APIs? (document if so)
1521
860
  async finalizeTransformation() {
1522
861
  this.importer.finalize();
@@ -1547,7 +886,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1547
886
  /** Override of [IModelExportHandler.shouldExportRelationship]($transformer) that is called to determine if a [Relationship]($backend) should be exported.
1548
887
  * @note Reaching this point means that the relationship has passed the standard exclusion checks in [IModelExporter]($transformer).
1549
888
  */
1550
- shouldExportRelationship(_sourceRelationship) {
889
+ async shouldExportRelationship(_sourceRelationship) {
1551
890
  return true;
1552
891
  }
1553
892
  /** Override of [IModelExportHandler.onExportRelationship]($transformer) that imports a relationship into the target iModel when it is exported from the source iModel.
@@ -1557,23 +896,22 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1557
896
  const sourceFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.sourceId);
1558
897
  const targetFedGuid = this.sourceDb.elements.getFederationGuidFromId(sourceRelationship.targetId);
1559
898
  const targetRelationshipProps = this.onTransformRelationship(sourceRelationship);
1560
- const targetRelationshipInstanceId = this.importer.importRelationship(targetRelationshipProps);
899
+ const targetRelationshipInstanceId = await this.importer.importRelationship(targetRelationshipProps);
1561
900
  const provenanceDb = await this.getProvenanceDb();
1562
901
  if (!this._options.noProvenance &&
1563
902
  core_bentley_1.Id64.isValid(targetRelationshipInstanceId)) {
1564
- let provenance = !this._options.forceExternalSourceAspectProvenance
1565
- ? sourceFedGuid && targetFedGuid && `${sourceFedGuid}/${targetFedGuid}`
1566
- : undefined;
1567
- if (!provenance) {
1568
- const aspectProps = await this.initRelationshipProvenance(sourceRelationship, targetRelationshipInstanceId);
1569
- const foundEsaProps = await IModelTransformer.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
903
+ const needsEsaProvenance = !this._options
904
+ .forceExternalSourceAspectProvenance
905
+ ? !(sourceFedGuid && targetFedGuid)
906
+ : true;
907
+ if (needsEsaProvenance) {
908
+ const aspectProps = await this._provenanceManager.initRelationshipProvenance(sourceRelationship.id, targetRelationshipInstanceId, this._forceOldRelationshipProvenanceMethod);
909
+ const foundEsaProps = await ProvenanceManager_1.ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
1570
910
  // onExportRelationship doesn't need to call updateAspect if esaProps were found, because relationship provenance doesn't have the same concept of a version as element provenance (which uses last mod time on the elements).
1571
911
  if (undefined === foundEsaProps) {
1572
912
  aspectProps.id = provenanceDb.elements.insertAspect(aspectProps);
1573
913
  }
1574
- provenance = aspectProps;
1575
914
  }
1576
- this.markLastProvenance(provenance, { isRelationship: true });
1577
915
  }
1578
916
  }
1579
917
  /** Override of [IModelExportHandler.onDeleteRelationship]($transformer) that is called when [IModelExporter]($transformer) detects that a [Relationship]($backend) has been deleted from the source iModel.
@@ -1593,7 +931,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1593
931
  targetId: deletedRelData.targetIdInTarget,
1594
932
  })?.id;
1595
933
  if (id) {
1596
- this.importer.deleteRelationship({
934
+ await this.importer.deleteRelationship({
1597
935
  id,
1598
936
  classFullName: deletedRelData.classFullName,
1599
937
  });
@@ -1629,7 +967,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1629
967
  });
1630
968
  return targetRelationshipProps;
1631
969
  }
1632
- shouldExportElementAspect(aspect) {
970
+ async shouldExportElementAspect(aspect) {
1633
971
  // This override is needed to ensure that aspects are not exported if their element is not exported.
1634
972
  // This is needed in case DetachedExportElementAspectsStrategy is used.
1635
973
  return this.context.findTargetElementId(aspect.element.id) !== core_bentley_1.Id64.invalid;
@@ -1642,7 +980,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1642
980
  if (!(await this.doAllReferencesExistInTarget(sourceAspect))) {
1643
981
  this._partiallyCommittedAspectIds.add(sourceAspect.id);
1644
982
  }
1645
- const targetId = this.importer.importElementUniqueAspect(targetAspectProps);
983
+ const targetId = await this.importer.importElementUniqueAspect(targetAspectProps);
1646
984
  this.context.remapElementAspect(sourceAspect.id, targetId);
1647
985
  }
1648
986
  /** Override of [IModelExportHandler.onExportElementMultiAspects]($transformer) that imports ElementMultiAspects into the target iModel when they are exported from the source iModel.
@@ -1652,13 +990,13 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1652
990
  async onExportElementMultiAspects(sourceAspects) {
1653
991
  // Transform source ElementMultiAspects into target ElementAspectProps
1654
992
  const targetAspectPropsArray = sourceAspects.map(async (srcA) => this.onTransformElementAspect(srcA));
1655
- sourceAspects.forEach(async (a) => {
993
+ for (const a of sourceAspects) {
1656
994
  if (!(await this.doAllReferencesExistInTarget(a))) {
1657
995
  this._partiallyCommittedAspectIds.add(a.id);
1658
996
  }
1659
- });
997
+ }
1660
998
  // const targetAspectsToImport = targetAspectPropsArray.filter((targetAspect, i) => hasEntityChanged(sourceAspects[i], targetAspect));
1661
- const targetIds = this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
999
+ const targetIds = await this.importer.importElementMultiAspects(await Promise.all(targetAspectPropsArray), (a) => {
1662
1000
  const isExternalSourceAspectFromTransformer = a instanceof core_backend_1.ExternalSourceAspect &&
1663
1001
  a.scope?.id === this.targetScopeElementId;
1664
1002
  return (!this._options.includeSourceProvenance ||
@@ -1682,7 +1020,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1682
1020
  /** Override of [IModelExportHandler.shouldExportSchema]($transformer) that is called to determine if a schema should be exported
1683
1021
  * @note the default behavior doesn't import schemas older than those already in the target
1684
1022
  */
1685
- shouldExportSchema(schemaKey) {
1023
+ async shouldExportSchema(schemaKey) {
1686
1024
  const versionInTarget = this.targetDb.querySchemaVersion(schemaKey.name);
1687
1025
  if (versionInTarget === undefined)
1688
1026
  return true;
@@ -1768,7 +1106,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1768
1106
  return this.exporter.exportFonts();
1769
1107
  }
1770
1108
  /** Override of [IModelExportHandler.onExportFont]($transformer) that imports a font into the target iModel when it is exported from the source iModel. */
1771
- onExportFont(font, _isUpdate) {
1109
+ async onExportFont(font, _isUpdate) {
1772
1110
  this.context.importFont(font.id);
1773
1111
  }
1774
1112
  /** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
@@ -1788,11 +1126,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1788
1126
  /** Override of [IModelExportHandler.shouldExportCodeSpec]($transformer) that is called to determine if a CodeSpec should be exported from the source iModel.
1789
1127
  * @note Reaching this point means that the CodeSpec has passed the standard exclusion checks in [IModelExporter]($transformer).
1790
1128
  */
1791
- shouldExportCodeSpec(_sourceCodeSpec) {
1129
+ async shouldExportCodeSpec(_sourceCodeSpec) {
1792
1130
  return true;
1793
1131
  }
1794
1132
  /** Override of [IModelExportHandler.onExportCodeSpec]($transformer) that imports a CodeSpec into the target iModel when it is exported from the source iModel. */
1795
- onExportCodeSpec(sourceCodeSpec) {
1133
+ async onExportCodeSpec(sourceCodeSpec) {
1796
1134
  this.context.importCodeSpec(sourceCodeSpec.id);
1797
1135
  }
1798
1136
  /** Recursively import all Elements and sub-Models that descend from the specified Subject */
@@ -1836,7 +1174,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1836
1174
  * @returns void
1837
1175
  */
1838
1176
  async processChangesets() {
1839
- await this.forEachTrackedElement((sourceElementId, targetElementId) => {
1177
+ await this._provenanceManager.forEachTrackedElement((sourceElementId, targetElementId) => {
1840
1178
  this.context.remapElement(sourceElementId, targetElementId);
1841
1179
  });
1842
1180
  if (this.exporter.sourceDbChanges)
@@ -1994,8 +1332,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
1994
1332
  targetIdInTarget: targetIdOfRelationshipInTarget,
1995
1333
  });
1996
1334
  }
1997
- else if (this.sourceDb === (await this.getProvenanceSourceDb())) {
1998
- const relProvenance = await this._queryProvenanceForRelationship(changedInstanceId, {
1335
+ else if (this.sourceDb ===
1336
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1337
+ const relProvenance = await this._provenanceManager.queryProvenanceForRelationship(changedInstanceId, {
1999
1338
  classFullName: classFullName ?? "",
2000
1339
  sourceId: sourceIdOfRelationshipInSource,
2001
1340
  targetId: targetIdOfRelationshipInSource,
@@ -2011,8 +1350,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2011
1350
  else {
2012
1351
  let targetId = await getTargetIdFromSourceId(changedInstanceId);
2013
1352
  if (targetId === undefined &&
2014
- this.sourceDb === (await this.getProvenanceSourceDb())) {
2015
- targetId = await this._queryProvenanceForElement(changedInstanceId);
1353
+ this.sourceDb ===
1354
+ (await this._provenanceManager.getProvenanceSourceDb())) {
1355
+ targetId =
1356
+ await this._provenanceManager.queryProvenanceForElement(changedInstanceId);
2016
1357
  }
2017
1358
  // since we are processing one changeset at a time, we can see local source deletes
2018
1359
  // of entities that were never synced and can be safely ignored
@@ -2071,12 +1412,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2071
1412
  ` which is changeset #${syncVersion.index}. The transformer expected` +
2072
1413
  ` #${syncVersion.index + 1}.`);
2073
1414
  }
2074
- nodeAssert(this._targetScopeProvenanceProps, "_targetScopeProvenanceProps should be set by now");
2075
- const changesetsToSkip = (await this.getIsReverseSynchronization())
2076
- ? this._targetScopeProvenanceProps.jsonProperties
2077
- .pendingReverseSyncChangesetIndices
2078
- : this._targetScopeProvenanceProps.jsonProperties
2079
- .pendingSyncChangesetIndices;
1415
+ const changesetsToSkip = await this._provenanceManager.getChangesetsToSkip();
2080
1416
  core_bentley_1.Logger.logTrace(loggerCategory, `changesets to skip: ${changesetsToSkip}`);
2081
1417
  this._changesetRanges = (0, Algo_1.rangesFromRangeAndSkipped)(startChangesetIndex, endChangesetIndex, changesetsToSkip);
2082
1418
  core_bentley_1.Logger.logTrace(loggerCategory, `ranges: ${this._changesetRanges}`);
@@ -2129,6 +1465,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2129
1465
  * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
2130
1466
  */
2131
1467
  async processAll() {
1468
+ this._targetElementsImportedInCurrentTransform.clear();
1469
+ // processAll always has changes to process, so mark it as such for version tracking
1470
+ this._sourceChangeDataState = "has-changes";
2132
1471
  await this.exporter.exportCodeSpecs();
2133
1472
  await this.exporter.exportFonts();
2134
1473
  if (this._options.skipPropagateChangesToRootElements) {
@@ -2145,7 +1484,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2145
1484
  await this.completePartiallyCommittedAspects();
2146
1485
  await this.exporter.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
2147
1486
  if (this._options.forceExternalSourceAspectProvenance &&
2148
- this.shouldDetectDeletes()) {
1487
+ (await this.shouldDetectDeletes())) {
2149
1488
  core_bentley_1.Logger.logWarning(loggerCategory, "This workflows was deprecated in v1 and is no longer supported");
2150
1489
  }
2151
1490
  if (this._options.optimizeGeometry)
@@ -2153,21 +1492,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2153
1492
  this.importer.computeProjectExtents();
2154
1493
  await this.finalizeTransformation();
2155
1494
  }
2156
- /** previous provenance, either a federation guid, a `${sourceFedGuid}/${targetFedGuid}` pair, or required aspect props */
2157
- _lastProvenanceEntityInfo = nullLastProvenanceEntityInfo;
2158
- markLastProvenance(sourceAspect, { isRelationship = false }) {
2159
- this._lastProvenanceEntityInfo =
2160
- typeof sourceAspect === "string"
2161
- ? sourceAspect
2162
- : {
2163
- entityId: sourceAspect.element.id,
2164
- aspectId: sourceAspect.id,
2165
- aspectVersion: sourceAspect.version ?? "",
2166
- aspectKind: isRelationship
2167
- ? core_backend_1.ExternalSourceAspect.Kind.Relationship
2168
- : core_backend_1.ExternalSourceAspect.Kind.Element,
2169
- };
2170
- }
2171
1495
  /** Export changes from the source iModel and import the transformed entities into the target iModel.
2172
1496
  * Inserts, updates, and deletes are determined by inspecting the changeset(s).
2173
1497
  * @note the transformer assumes that you saveChanges after processing changes. You should not
@@ -2178,6 +1502,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
2178
1502
  * @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.
2179
1503
  */
2180
1504
  async processChanges(options) {
1505
+ this._targetElementsImportedInCurrentTransform.clear();
2181
1506
  // must wait for initialization of synchronization provenance data
2182
1507
  await this.exporter.exportChanges(await this.getExportInitOpts(options));
2183
1508
  await this.completePartiallyCommittedElements();