@itwin/imodel-transformer 2.0.0-dev.7 → 2.0.0-dev.71

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 (132) hide show
  1. package/README.md +13 -27
  2. package/lib/cjs/Algo.d.ts +0 -4
  3. package/lib/cjs/Algo.d.ts.map +1 -1
  4. package/lib/cjs/Algo.js +0 -11
  5. package/lib/cjs/Algo.js.map +1 -1
  6. package/lib/cjs/BigMap.d.ts.map +1 -1
  7. package/lib/cjs/BigMap.js.map +1 -1
  8. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  9. package/lib/cjs/BranchProvenanceInitializer.js +26 -26
  10. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  11. package/lib/cjs/ChangedElementForest.d.ts +14 -0
  12. package/lib/cjs/ChangedElementForest.d.ts.map +1 -0
  13. package/lib/cjs/ChangedElementForest.js +86 -0
  14. package/lib/cjs/ChangedElementForest.js.map +1 -0
  15. package/lib/cjs/ChangesetScanner.d.ts +141 -0
  16. package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
  17. package/lib/cjs/ChangesetScanner.js +187 -0
  18. package/lib/cjs/ChangesetScanner.js.map +1 -0
  19. package/lib/cjs/ECReferenceTypesCache.d.ts +0 -4
  20. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  21. package/lib/cjs/ECReferenceTypesCache.js +61 -111
  22. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  23. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
  24. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
  25. package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
  26. package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
  27. package/lib/cjs/ElementAspectCleanup.js +87 -0
  28. package/lib/cjs/ElementAspectCleanup.js.map +1 -0
  29. package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
  30. package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
  31. package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
  32. package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
  33. package/lib/cjs/ElementAspectExportProcessor.d.ts +44 -0
  34. package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
  35. package/lib/cjs/ElementAspectExportProcessor.js +152 -0
  36. package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
  37. package/lib/cjs/ElementBulkDelete.d.ts +30 -0
  38. package/lib/cjs/ElementBulkDelete.d.ts.map +1 -0
  39. package/lib/cjs/ElementBulkDelete.js +358 -0
  40. package/lib/cjs/ElementBulkDelete.js.map +1 -0
  41. package/lib/cjs/EntityExistenceCache.d.ts +43 -0
  42. package/lib/cjs/EntityExistenceCache.d.ts.map +1 -0
  43. package/lib/cjs/EntityExistenceCache.js +91 -0
  44. package/lib/cjs/EntityExistenceCache.js.map +1 -0
  45. package/lib/cjs/EntityUnifier.d.ts +7 -7
  46. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  47. package/lib/cjs/EntityUnifier.js +55 -24
  48. package/lib/cjs/EntityUnifier.js.map +1 -1
  49. package/lib/cjs/IModelCloneContext.d.ts +9 -3
  50. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  51. package/lib/cjs/IModelCloneContext.js +56 -22
  52. package/lib/cjs/IModelCloneContext.js.map +1 -1
  53. package/lib/cjs/IModelExporter.d.ts +180 -43
  54. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  55. package/lib/cjs/IModelExporter.js +946 -247
  56. package/lib/cjs/IModelExporter.js.map +1 -1
  57. package/lib/cjs/IModelImporter.d.ts +75 -30
  58. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  59. package/lib/cjs/IModelImporter.js +292 -132
  60. package/lib/cjs/IModelImporter.js.map +1 -1
  61. package/lib/cjs/IModelTransformContext.d.ts +54 -0
  62. package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
  63. package/lib/cjs/IModelTransformContext.js +3 -0
  64. package/lib/cjs/IModelTransformContext.js.map +1 -0
  65. package/lib/cjs/IModelTransformer.d.ts +184 -206
  66. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  67. package/lib/cjs/IModelTransformer.js +819 -1278
  68. package/lib/cjs/IModelTransformer.js.map +1 -1
  69. package/lib/cjs/IModelTransformerError.d.ts +84 -0
  70. package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
  71. package/lib/cjs/IModelTransformerError.js +93 -0
  72. package/lib/cjs/IModelTransformerError.js.map +1 -0
  73. package/lib/cjs/ProvenanceManager.d.ts +177 -0
  74. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  75. package/lib/cjs/ProvenanceManager.js +719 -0
  76. package/lib/cjs/ProvenanceManager.js.map +1 -0
  77. package/lib/cjs/SourceClassKinds.d.ts +22 -0
  78. package/lib/cjs/SourceClassKinds.d.ts.map +1 -0
  79. package/lib/cjs/SourceClassKinds.js +60 -0
  80. package/lib/cjs/SourceClassKinds.js.map +1 -0
  81. package/lib/cjs/SyncTypeResolver.d.ts +32 -0
  82. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  83. package/lib/cjs/SyncTypeResolver.js +92 -0
  84. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  85. package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
  86. package/lib/cjs/TransformerLoggerCategory.js +1 -1
  87. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  88. package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
  89. package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
  90. package/lib/cjs/TransformerPackageMetadata.js +47 -0
  91. package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
  92. package/lib/cjs/imodel-transformer.d.ts +28 -0
  93. package/lib/cjs/imodel-transformer.d.ts.map +1 -1
  94. package/lib/cjs/imodel-transformer.js +61 -34
  95. package/lib/cjs/imodel-transformer.js.map +1 -1
  96. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
  97. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
  98. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
  99. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
  100. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
  101. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
  102. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
  103. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
  104. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
  105. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
  106. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
  107. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
  108. package/lib/cjs/schema-processing.d.ts +7 -0
  109. package/lib/cjs/schema-processing.d.ts.map +1 -0
  110. package/lib/cjs/schema-processing.js +27 -0
  111. package/lib/cjs/schema-processing.js.map +1 -0
  112. package/package.json +70 -57
  113. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
  114. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
  115. package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
  116. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
  117. package/lib/cjs/ElementCascadingDeleter.d.ts +0 -22
  118. package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
  119. package/lib/cjs/ElementCascadingDeleter.js +0 -57
  120. package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
  121. package/lib/cjs/EntityMap.d.ts +0 -26
  122. package/lib/cjs/EntityMap.d.ts.map +0 -1
  123. package/lib/cjs/EntityMap.js +0 -54
  124. package/lib/cjs/EntityMap.js.map +0 -1
  125. package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
  126. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
  127. package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
  128. package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
  129. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
  130. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
  131. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
  132. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
@@ -13,10 +13,22 @@ const core_bentley_1 = require("@itwin/core-bentley");
13
13
  const core_common_1 = require("@itwin/core-common");
14
14
  const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
15
15
  const core_backend_1 = require("@itwin/core-backend");
16
- const assert = require("assert");
17
- const ElementCascadingDeleter_1 = require("./ElementCascadingDeleter");
16
+ const node_assert_1 = require("node:assert");
17
+ const ElementAspectCleanup_1 = require("./ElementAspectCleanup");
18
18
  const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
19
+ const IModelTransformerError_1 = require("./IModelTransformerError");
20
+ const ElementBulkDelete_1 = require("./ElementBulkDelete");
19
21
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter;
22
+ /** Lists the first kept references for a log message. */
23
+ function formatKeptReferences(keptReferences) {
24
+ const maxListed = 10;
25
+ const listed = [...keptReferences]
26
+ .slice(0, maxListed)
27
+ .map(([elementId, referencingId]) => `${elementId} (referenced by ${referencingId})`)
28
+ .join(", ");
29
+ const unlisted = keptReferences.size - maxListed;
30
+ return unlisted > 0 ? `${listed}, and ${unlisted} more` : listed;
31
+ }
20
32
  /** Base class for importing data into an iModel.
21
33
  * @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md)
22
34
  * @see [IModelExporter]($transformer)
@@ -26,6 +38,23 @@ const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IMo
26
38
  class IModelImporter {
27
39
  /** The read/write target iModel. */
28
40
  targetDb;
41
+ /** The [[EditTxn]] used for write operations on the target iModel.
42
+ * @beta
43
+ */
44
+ _editTxn;
45
+ /** The [[EditTxn]] used for write operations on the target iModel.
46
+ * @beta
47
+ */
48
+ get editTxn() {
49
+ return this._editTxn;
50
+ }
51
+ _elementAspectCleanup;
52
+ /** Deletes replaceable ElementAspects through this importer's customized deletion callback.
53
+ * @internal
54
+ */
55
+ get elementAspectCleanup() {
56
+ return this._elementAspectCleanup;
57
+ }
29
58
  /** resolved initialization options for the importer
30
59
  * @beta
31
60
  */
@@ -39,6 +68,7 @@ class IModelImporter {
39
68
  * To resolve code values to their intended values call [[IModelImporter.resolveDuplicateCodeValues]].
40
69
  */
41
70
  _duplicateCodeValueMap;
71
+ _entityExistenceCaches = new Set();
42
72
  /**
43
73
  * A set of elementIds that the transformer adds to while exporting elements to indicate that the element already exists in the target.
44
74
  * Defaults to an empty set.
@@ -52,9 +82,10 @@ class IModelImporter {
52
82
  * @note This set should stay small, as right after the transformer pushes to it, the importer will remove from the set.
53
83
  */
54
84
  _elementsToUpdateDuringPreserveIds = new Set([]);
55
- /** The set of elements that should not be updated by this IModelImporter.
56
- * Defaults to an empty set.
85
+ /** The set of target elements that this importer must not directly update or request for deletion.
86
+ * Defaults to an empty set and is populated by consumers or custom transformers.
57
87
  * @note Adding an element to this set is typically necessary when remapping a source element to one that already exists in the target and already has the desired properties.
88
+ * @note Deletion protection applies only when the exact element ID is passed to [[deleteElement]] or [[deleteElements]]. It does not protect the element from deletion through a parent, containing model, or other cascading dependency. Consumers that need to preserve such an element must also add every requested deletion root whose cascade would delete it.
58
89
  */
59
90
  doNotUpdateElementIds = new Set([]);
60
91
  /** This set is ONLY used for elements that are always present even in an "empty" iModel. We will use this set to filter out changes to root elements if [[IModelTransformOptions.skipPropagateChangesToRootElements]] is false. */
@@ -72,11 +103,12 @@ class IModelImporter {
72
103
  "geometryGuid", // cannot compare GeometricModel.GeometryGuid values across iModels
73
104
  ]);
74
105
  /** Construct a new IModelImporter
75
- * @param targetDb The target IModelDb
106
+ * @param editTxn The [[EditTxn]] to use for write operations on the target iModel. The target db is derived from `editTxn.iModel`.
76
107
  * @param options The options that specify how the import should be done.
77
108
  */
78
- constructor(targetDb, options) {
79
- this.targetDb = targetDb;
109
+ constructor(editTxn, options) {
110
+ this.targetDb = editTxn.iModel;
111
+ this._editTxn = editTxn;
80
112
  this.options = {
81
113
  autoExtendProjectExtents: options?.autoExtendProjectExtents ?? true,
82
114
  preserveElementIdsForFiltering: options?.preserveElementIdsForFiltering ?? false,
@@ -84,6 +116,19 @@ class IModelImporter {
84
116
  skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
85
117
  };
86
118
  this._duplicateCodeValueMap = new Map();
119
+ this._elementAspectCleanup = new ElementAspectCleanup_1.ElementAspectCleanup(this.targetDb, this._editTxn, async (aspect) => this.onDeleteElementAspect(aspect));
120
+ }
121
+ /** Register a transformation-scoped existence cache for delete invalidation.
122
+ * @internal
123
+ */
124
+ registerEntityExistenceCache(cache) {
125
+ this._entityExistenceCaches.add(cache);
126
+ }
127
+ /** Stop invalidating a transformation-scoped existence cache.
128
+ * @internal
129
+ */
130
+ unregisterEntityExistenceCache(cache) {
131
+ this._entityExistenceCaches.delete(cache);
87
132
  }
88
133
  /**
89
134
  * Checks [[IModelImportOptions.skipPropagateChangesToRootElements]], [[IModelImporter.rootElementIds]], and [[IModelImporter.doNotUpdateElementIds]] and returns true for 'do not update', false for do 'update'.
@@ -106,9 +151,15 @@ class IModelImporter {
106
151
  this._elementsToUpdateDuringPreserveIds.add(elementId);
107
152
  }
108
153
  /** Import the specified ModelProps (either as an insert or an update) into the target iModel. */
109
- importModel(modelProps) {
154
+ async importModel(modelProps) {
110
155
  if (undefined === modelProps.id || !core_bentley_1.Id64.isValidId64(modelProps.id))
111
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Model Id not provided, should be the same as the ModeledElementId");
156
+ core_bentley_1.ITwinError.throwError({
157
+ iTwinErrorId: {
158
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
159
+ key: IModelTransformerError_1.IModelTransformerError.InvalidModelId,
160
+ },
161
+ message: "Model Id not provided, should be the same as the ModeledElementId",
162
+ });
112
163
  if (this.doNotUpdateElement(modelProps.id)) {
113
164
  core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target model ${modelProps.id}`);
114
165
  return;
@@ -116,14 +167,14 @@ class IModelImporter {
116
167
  try {
117
168
  const model = this.targetDb.models.getModel(modelProps.id); // throws IModelError.NotFound if model does not exist
118
169
  if (hasEntityChanged(model, modelProps, this._modelPropertiesToIgnore)) {
119
- this.onUpdateModel(modelProps);
170
+ await this.onUpdateModel(modelProps);
120
171
  }
121
172
  }
122
173
  catch (error) {
123
174
  // catch NotFound error and insertModel
124
175
  if (error instanceof core_common_1.IModelError &&
125
176
  error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
126
- this.onInsertModel(modelProps);
177
+ await this.onInsertModel(modelProps);
127
178
  return;
128
179
  }
129
180
  throw error;
@@ -132,29 +183,35 @@ class IModelImporter {
132
183
  /** Create a new Model from the specified ModelProps and insert it into the target iModel.
133
184
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertModel`.
134
185
  */
135
- onInsertModel(modelProps) {
186
+ async onInsertModel(modelProps) {
136
187
  try {
137
- const modelId = this.targetDb.models.insertModel(modelProps);
188
+ const modelId = this._editTxn.insertModel(modelProps);
138
189
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatModelForLogger(modelProps)}`);
139
- this.trackProgress();
190
+ await this.trackProgress();
140
191
  return modelId;
141
192
  }
142
193
  catch (error) {
143
194
  if (!this.targetDb.containsClass(modelProps.classFullName)) {
144
- // replace standard insert error with something more helpful
145
- const errorMessage = `Model class "${modelProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
146
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
195
+ // Translate only confirmed missing-class failures; otherwise retain the backend error's identity.
196
+ core_bentley_1.ITwinError.throwError({
197
+ iTwinErrorId: {
198
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
199
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
200
+ },
201
+ message: `Model class "${modelProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
202
+ cause: error,
203
+ });
147
204
  }
148
- throw error; // throw original error
205
+ throw error;
149
206
  }
150
207
  }
151
208
  /** Update an existing Model in the target iModel from the specified ModelProps.
152
209
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateModel`.
153
210
  */
154
- onUpdateModel(modelProps) {
155
- this.targetDb.models.updateModel(modelProps);
211
+ async onUpdateModel(modelProps) {
212
+ this._editTxn.updateModel(modelProps);
156
213
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatModelForLogger(modelProps)}`);
157
- this.trackProgress();
214
+ await this.trackProgress();
158
215
  }
159
216
  /** Format a Model for the Logger. */
160
217
  formatModelForLogger(modelProps) {
@@ -165,18 +222,18 @@ class IModelImporter {
165
222
  * Tries to update an element with the specified element properties.
166
223
  * If a duplicate code error occurs, it assigns a new unique code value and retries the update
167
224
  */
168
- tryUpdateElement(elemProps) {
225
+ async tryUpdateElement(elemProps) {
169
226
  try {
170
- this.onUpdateElement(elemProps);
227
+ await this.onUpdateElement(elemProps);
171
228
  }
172
229
  catch (err) {
173
230
  if (err.errorNumber === core_bentley_1.IModelStatus.DuplicateCode) {
174
- assert(elemProps.code.value !== undefined, "NULL code values are always considered unique and cannot clash");
231
+ (0, node_assert_1.strict)(elemProps.code.value !== undefined, "NULL code values are always considered unique and cannot clash");
175
232
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
176
233
  this._duplicateCodeValueMap.set(elemProps.id, elemProps.code.value);
177
234
  // Using NULL code values as an alternative is not valid because definition elements cannot have NULL code values.
178
235
  elemProps.code.value = core_bentley_1.Guid.createValue();
179
- this.onUpdateElement(elemProps);
236
+ await this.onUpdateElement(elemProps);
180
237
  }
181
238
  else {
182
239
  throw err;
@@ -184,7 +241,7 @@ class IModelImporter {
184
241
  }
185
242
  }
186
243
  /** Import the specified ElementProps (either as an insert or an update) into the target iModel. */
187
- importElement(elementProps) {
244
+ async importElement(elementProps) {
188
245
  if (undefined !== elementProps.id &&
189
246
  this.doNotUpdateElement(elementProps.id)) {
190
247
  core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target element ${elementProps.id}`);
@@ -192,7 +249,13 @@ class IModelImporter {
192
249
  }
193
250
  if (this.options.preserveElementIdsForFiltering) {
194
251
  if (elementProps.id === undefined) {
195
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "elementProps.id must be defined during a preserveIds operation");
252
+ core_bentley_1.ITwinError.throwError({
253
+ iTwinErrorId: {
254
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
255
+ key: IModelTransformerError_1.IModelTransformerError.ElementIdRequired,
256
+ },
257
+ message: "elementProps.id must be defined during a preserveIds operation",
258
+ });
196
259
  }
197
260
  // Categories are the only element that onInserted will immediately insert a new element (their default subcategory)
198
261
  // since default subcategories always exist and always will be inserted after their categories, we treat them as an update
@@ -200,24 +263,24 @@ class IModelImporter {
200
263
  // Always present elements (0xe, 0x1, 0x10) also will be updated to prevent duplicate inserts.
201
264
  if ((isSubCategory(elementProps) && isDefaultSubCategory(elementProps)) ||
202
265
  this._rootElementIds.has(elementProps.id)) {
203
- this.onUpdateElement(elementProps);
266
+ await this.onUpdateElement(elementProps);
204
267
  }
205
268
  else {
206
269
  if (this._elementsToUpdateDuringPreserveIds.has(elementProps.id)) {
207
- this.tryUpdateElement(elementProps);
270
+ await this.tryUpdateElement(elementProps);
208
271
  this._elementsToUpdateDuringPreserveIds.delete(elementProps.id);
209
272
  }
210
273
  else {
211
- this.onInsertElement(elementProps);
274
+ await this.onInsertElement(elementProps);
212
275
  }
213
276
  }
214
277
  }
215
278
  else {
216
279
  if (undefined !== elementProps.id) {
217
- this.tryUpdateElement(elementProps);
280
+ await this.tryUpdateElement(elementProps);
218
281
  }
219
282
  else {
220
- elementProps.id = this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
283
+ elementProps.id = await this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
221
284
  }
222
285
  }
223
286
  return elementProps.id;
@@ -226,15 +289,15 @@ class IModelImporter {
226
289
  * @returns The Id of the newly inserted Element.
227
290
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertElement`.
228
291
  */
229
- onInsertElement(elementProps) {
292
+ async onInsertElement(elementProps) {
230
293
  try {
231
- const elementId = this.targetDb.elements.insertElement(elementProps, {
294
+ const elementId = this._editTxn.insertElement(elementProps, {
232
295
  forceUseId: this.options.preserveElementIdsForFiltering,
233
296
  });
234
297
  // set the id like [IModelDb.insertElement]($backend), does, the raw nativeDb method does not
235
298
  elementProps.id = elementId;
236
299
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementForLogger(elementProps)}`);
237
- this.trackProgress();
300
+ await this.trackProgress();
238
301
  if (this.options.simplifyElementGeometry) {
239
302
  this.targetDb.simplifyElementGeometry({
240
303
  id: elementId,
@@ -246,23 +309,34 @@ class IModelImporter {
246
309
  }
247
310
  catch (error) {
248
311
  if (!this.targetDb.containsClass(elementProps.classFullName)) {
249
- // replace standard insert error with something more helpful
250
- const errorMessage = `Element class "${elementProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
251
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
312
+ core_bentley_1.ITwinError.throwError({
313
+ iTwinErrorId: {
314
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
315
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
316
+ },
317
+ message: `Element class "${elementProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
318
+ cause: error,
319
+ });
252
320
  }
253
- throw error; // throw original error
321
+ throw error;
254
322
  }
255
323
  }
256
324
  /** Update an existing Element in the target iModel from the specified ElementProps.
257
325
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateElement`.
258
326
  */
259
- onUpdateElement(elementProps) {
327
+ async onUpdateElement(elementProps) {
260
328
  if (!elementProps.id) {
261
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "ElementId not provided");
329
+ core_bentley_1.ITwinError.throwError({
330
+ iTwinErrorId: {
331
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
332
+ key: IModelTransformerError_1.IModelTransformerError.ElementIdRequired,
333
+ },
334
+ message: "ElementId not provided",
335
+ });
262
336
  }
263
- this.targetDb.elements.updateElement(elementProps);
337
+ this._editTxn.updateElement(elementProps);
264
338
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementForLogger(elementProps)}`);
265
- this.trackProgress();
339
+ await this.trackProgress();
266
340
  if (this.options.simplifyElementGeometry) {
267
341
  this.targetDb.simplifyElementGeometry({
268
342
  id: elementProps.id,
@@ -271,34 +345,83 @@ class IModelImporter {
271
345
  core_bentley_1.Logger.logInfo(loggerCategory, `Simplified element geometry for ${this.formatElementForLogger(elementProps)}`);
272
346
  }
273
347
  }
274
- /** Delete the specified Element (and all its children) from the target iModel.
275
- * Will delete special elements like definition elements and subjects.
276
- * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElement`.
348
+ /** Deletes one target element tree through [[deleteElements]]. */
349
+ async deleteElement(elementId) {
350
+ await this.deleteElements(new Set([elementId]));
351
+ }
352
+ /** Adds roots required by code-scope dependencies, then deletes the target element trees in as few native operations as their references allow.
353
+ * Usually one native operation is enough. When a tree contains a definition, such as a category, that another deleted element uses, the elements that use it are deleted first.
354
+ * Elements that something outside the deleted trees still references are kept, with a warning, and the rest are deleted.
355
+ * @note An override must call `super.onDeleteElements` once to perform the deletion.
277
356
  */
278
- onDeleteElement(elementId) {
279
- (0, ElementCascadingDeleter_1.deleteElementTreeCascade)(this.targetDb, elementId);
280
- core_bentley_1.Logger.logInfo(loggerCategory, `Deleted element ${elementId} and its descendants`);
281
- this.trackProgress();
282
- }
283
- /** Delete the specified Element from the target iModel. */
284
- deleteElement(elementId) {
285
- if (this.doNotUpdateElement(elementId)) {
286
- core_bentley_1.Logger.logInfo(loggerCategory, `Do not delete target element ${elementId}`);
357
+ async onDeleteElements(elementIds) {
358
+ const plan = await (0, ElementBulkDelete_1.planBulkDelete)(this.targetDb, elementIds);
359
+ if (plan.keptReferences.size > 0) {
360
+ core_bentley_1.Logger.logWarning(loggerCategory, `Kept ${plan.keptReferences.size} elements that elements outside the deleted trees still reference, and the elements that contain them: ${formatKeptReferences(plan.keptReferences)}`);
361
+ }
362
+ if (plan.phases.length === 0)
287
363
  return;
364
+ let rootCount = 0;
365
+ for (const roots of plan.phases) {
366
+ rootCount += roots.length;
367
+ this.deleteElementRoots(roots);
368
+ }
369
+ core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${elementIds.size} requested element trees using ${rootCount} native deletion roots in ${plan.phases.length} native operations`);
370
+ await this.trackProgress(elementIds.size);
371
+ }
372
+ deleteElementRoots(roots) {
373
+ const result = this._editTxn.deleteElements(roots);
374
+ if (result.status !== core_backend_1.BulkDeleteElementsStatus.Success) {
375
+ core_bentley_1.ITwinError.throwError({
376
+ iTwinErrorId: {
377
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
378
+ key: IModelTransformerError_1.IModelTransformerError.ElementBulkDeleteFailed,
379
+ },
380
+ message: `Bulk element deletion failed: status ${core_backend_1.BulkDeleteElementsStatus[result.status]}, ` +
381
+ `SQLite status ${result.sqlDeleteStatus}, failed element IDs: ${[
382
+ ...result.failedIds,
383
+ ].join(", ")}`,
384
+ status: result.status,
385
+ sqlDeleteStatus: result.sqlDeleteStatus,
386
+ failedIds: new Set(result.failedIds),
387
+ });
288
388
  }
289
- this.onDeleteElement(elementId);
389
+ }
390
+ /** Deletes target element trees, using one native operation unless references between the trees require more.
391
+ * Requested roots in [[doNotUpdateElementIds]] are skipped. This does not prevent an element in that set from being deleted as part of another root's cascade.
392
+ * Elements that something outside the deleted trees still references are kept, along with their child elements and sub-models and the elements that contain them. A warning lists them.
393
+ * @throws [[ElementBulkDeleteError]] if a native operation fails for any root, for example because geometry outside the deleted trees uses a geometry part in them, or because of a reference in a domain schema. The transformer does not check those references first. Deletions from that and earlier native operations stay pending in the caller-owned transaction. Abandon the transaction before retrying.
394
+ */
395
+ async deleteElements(elementIds) {
396
+ const idsToDelete = new Set();
397
+ for (const elementId of elementIds) {
398
+ if (!this.doNotUpdateElement(elementId)) {
399
+ idsToDelete.add(elementId);
400
+ continue;
401
+ }
402
+ core_bentley_1.Logger.logInfo(loggerCategory, `Do not delete target element ${elementId}`);
403
+ }
404
+ if (idsToDelete.size === 0)
405
+ return;
406
+ await this.onDeleteElements(idsToDelete);
407
+ // Element deletion cascades can remove descendants and their submodels.
408
+ for (const cache of this._entityExistenceCaches)
409
+ cache.clearDb(this.targetDb);
290
410
  }
291
411
  /** Delete the specified Model from the target iModel.
292
412
  * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteModel`.
293
413
  */
294
414
  async onDeleteModel(modelId) {
295
- this.targetDb.models.deleteModel(modelId);
415
+ this._editTxn.deleteModel(modelId);
296
416
  core_bentley_1.Logger.logInfo(loggerCategory, `Deleted model ${modelId}`);
297
- this.trackProgress();
417
+ await this.trackProgress();
298
418
  }
299
419
  /** Delete the specified Model from the target iModel. */
300
420
  async deleteModel(modelId) {
301
421
  await this.onDeleteModel(modelId);
422
+ const modelReference = core_backend_1.EntityReferences.fromEntityType(modelId, core_common_1.ConcreteEntityTypes.Model);
423
+ for (const cache of this._entityExistenceCaches)
424
+ cache.invalidate(this.targetDb, modelReference);
302
425
  }
303
426
  /** Format an Element for the Logger. */
304
427
  formatElementForLogger(elementProps) {
@@ -310,14 +433,14 @@ class IModelImporter {
310
433
  return `${elementProps.classFullName} ${namePiece}[${elementProps.id}]`;
311
434
  }
312
435
  /** Import an ElementUniqueAspect into the target iModel. */
313
- importElementUniqueAspect(aspectProps) {
436
+ async importElementUniqueAspect(aspectProps) {
314
437
  const aspects = this.targetDb.elements.getAspects(aspectProps.element.id, aspectProps.classFullName);
315
438
  if (aspects.length === 0) {
316
439
  return this.onInsertElementAspect(aspectProps);
317
440
  }
318
441
  else if (hasEntityChanged(aspects[0], aspectProps)) {
319
442
  aspectProps.id = aspects[0].id;
320
- this.onUpdateElementAspect(aspectProps);
443
+ await this.onUpdateElementAspect(aspectProps);
321
444
  }
322
445
  return aspects[0].id;
323
446
  }
@@ -327,7 +450,7 @@ class IModelImporter {
327
450
  * @note For insert vs. update reasons, it is important to process all ElementMultiAspects owned by an Element at once since we don't have aspect-specific provenance.
328
451
  * @returns the array of ids of the resulting ElementMultiAspects, in the same order of the aspectPropsArray parameter
329
452
  */
330
- importElementMultiAspects(aspectPropsArray,
453
+ async importElementMultiAspects(aspectPropsArray,
331
454
  /** caller must use this to enforce any aspects added by IModelTransformer are not considered for update */
332
455
  filterFunc = () => true) {
333
456
  const result = new Array(aspectPropsArray.length).fill(undefined);
@@ -335,91 +458,96 @@ class IModelImporter {
335
458
  return result;
336
459
  }
337
460
  const elementId = aspectPropsArray[0].element.id;
338
- // Determine the set of ElementMultiAspect classes to consider
339
- const aspectClassFullNames = new Set();
340
- aspectPropsArray.forEach((aspectsProps) => {
341
- aspectClassFullNames.add(aspectsProps.classFullName);
461
+ const proposedAspectsByClass = new Map();
462
+ aspectPropsArray.forEach((props, index) => {
463
+ const proposedAspects = proposedAspectsByClass.get(props.classFullName) ?? [];
464
+ proposedAspects.push({ props, index });
465
+ proposedAspectsByClass.set(props.classFullName, proposedAspects);
342
466
  });
343
467
  // Handle ElementMultiAspects in groups by class
344
- aspectClassFullNames.forEach((aspectClassFullName) => {
345
- const proposedAspects = aspectPropsArray
346
- .map((props, index) => ({ props, index }))
347
- .filter(({ props }) => aspectClassFullName === props.classFullName);
468
+ for (const [aspectClassFullName, proposedAspects,] of proposedAspectsByClass) {
348
469
  const currentAspects = this.targetDb.elements
349
470
  .getAspects(elementId, aspectClassFullName)
350
471
  .map((props, index) => ({ props, index }))
351
472
  .filter(({ props }) => filterFunc(props));
352
473
  if (proposedAspects.length >= currentAspects.length) {
353
- proposedAspects.forEach(({ props, index: resultIndex }, index) => {
474
+ for (let index = 0; index < proposedAspects.length; index++) {
475
+ const { props, index: resultIndex } = proposedAspects[index];
354
476
  let id;
355
477
  if (index < currentAspects.length) {
356
478
  id = currentAspects[index].props.id;
357
479
  props.id = id;
358
480
  if (hasEntityChanged(currentAspects[index].props, props)) {
359
- this.onUpdateElementAspect(props);
481
+ await this.onUpdateElementAspect(props);
360
482
  }
361
483
  id = props.id;
362
484
  }
363
485
  else {
364
- id = this.onInsertElementAspect(props);
486
+ id = await this.onInsertElementAspect(props);
365
487
  }
366
488
  result[resultIndex] = id;
367
- });
489
+ }
368
490
  }
369
491
  else {
370
- currentAspects.forEach(({ props, index: resultIndex }, index) => {
371
- let id;
492
+ for (let index = 0; index < currentAspects.length; index++) {
493
+ const { props } = currentAspects[index];
372
494
  if (index < proposedAspects.length) {
373
- id = props.id;
374
- proposedAspects[index].props.id = id;
375
- if (hasEntityChanged(props, proposedAspects[index].props)) {
376
- this.onUpdateElementAspect(proposedAspects[index].props);
495
+ const { props: proposedProps, index: resultIndex } = proposedAspects[index];
496
+ const id = props.id;
497
+ proposedProps.id = id;
498
+ if (hasEntityChanged(props, proposedProps)) {
499
+ await this.onUpdateElementAspect(proposedProps);
377
500
  }
378
501
  result[resultIndex] = id;
379
502
  }
380
503
  else {
381
- this.onDeleteElementAspect(props);
504
+ await this.onDeleteElementAspect(props);
382
505
  }
383
- });
506
+ }
384
507
  }
385
- });
386
- assert(result.every((r) => typeof r !== undefined));
508
+ }
509
+ (0, node_assert_1.strict)(result.every((r) => r !== undefined));
387
510
  return result;
388
511
  }
389
512
  /** Insert the ElementAspect into the target iModel.
390
513
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertElementAspect`.
391
514
  */
392
- onInsertElementAspect(aspectProps) {
515
+ async onInsertElementAspect(aspectProps) {
393
516
  try {
394
- const id = this.targetDb.elements.insertAspect(aspectProps);
517
+ const id = this._editTxn.insertAspect(aspectProps);
395
518
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementAspectForLogger(aspectProps)}`);
396
- this.trackProgress();
519
+ await this.trackProgress();
397
520
  return id;
398
521
  }
399
522
  catch (error) {
400
523
  if (!this.targetDb.containsClass(aspectProps.classFullName)) {
401
- // replace standard insert error with something more helpful
402
- const errorMessage = `ElementAspect class "${aspectProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
403
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
524
+ core_bentley_1.ITwinError.throwError({
525
+ iTwinErrorId: {
526
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
527
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
528
+ },
529
+ message: `ElementAspect class "${aspectProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
530
+ cause: error,
531
+ });
404
532
  }
405
- throw error; // throw original error
533
+ throw error;
406
534
  }
407
535
  }
408
536
  /** Update the ElementAspect within the target iModel.
409
537
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateElementAspect`.
410
538
  */
411
- onUpdateElementAspect(aspectProps) {
412
- this.targetDb.elements.updateAspect(aspectProps);
539
+ async onUpdateElementAspect(aspectProps) {
540
+ this._editTxn.updateAspect(aspectProps);
413
541
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementAspectForLogger(aspectProps)}`);
414
- this.trackProgress();
542
+ await this.trackProgress();
415
543
  }
416
544
  /** Delete the specified ElementAspect from the target iModel.
417
545
  * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElementAspect`.
418
546
  */
419
- onDeleteElementAspect(targetElementAspect) {
420
- this.targetDb.elements.deleteAspect(targetElementAspect.id);
547
+ async onDeleteElementAspect(targetElementAspect) {
548
+ this._editTxn.deleteAspect(targetElementAspect.id);
421
549
  core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${this.formatElementAspectForLogger(targetElementAspect)}`);
422
- this.trackProgress();
550
+ await this.trackProgress();
423
551
  }
424
552
  /** Format an ElementAspect for the Logger. */
425
553
  formatElementAspectForLogger(elementAspectProps) {
@@ -428,7 +556,7 @@ class IModelImporter {
428
556
  /** Import the specified RelationshipProps (either as an insert or an update) into the target iModel.
429
557
  * @returns The instance Id of the inserted or updated Relationship.
430
558
  */
431
- importRelationship(relationshipProps) {
559
+ async importRelationship(relationshipProps) {
432
560
  if (undefined === relationshipProps.sourceId ||
433
561
  !core_bentley_1.Id64.isValidId64(relationshipProps.sourceId)) {
434
562
  core_bentley_1.Logger.logInfo(loggerCategory, `Ignoring ${relationshipProps.classFullName} instance because of invalid RelationshipProps.sourceId`);
@@ -449,7 +577,7 @@ class IModelImporter {
449
577
  // if relationship found, update it
450
578
  relationshipProps.id = relationship.id;
451
579
  if (hasEntityChanged(relationship, relationshipProps)) {
452
- this.onUpdateRelationship(relationshipProps);
580
+ await this.onUpdateRelationship(relationshipProps);
453
581
  }
454
582
  return relationshipProps.id;
455
583
  }
@@ -461,62 +589,75 @@ class IModelImporter {
461
589
  * @returns The instance Id of the newly inserted relationship.
462
590
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertRelationship`.
463
591
  */
464
- onInsertRelationship(relationshipProps) {
592
+ async onInsertRelationship(relationshipProps) {
465
593
  try {
466
- const targetRelInstanceId = this.targetDb.relationships.insertInstance(relationshipProps);
594
+ const targetRelInstanceId = this._editTxn.insertRelationship(relationshipProps);
467
595
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatRelationshipForLogger(relationshipProps)}`);
468
- this.trackProgress();
596
+ await this.trackProgress();
469
597
  return targetRelInstanceId;
470
598
  }
471
599
  catch (error) {
472
600
  if (!this.targetDb.containsClass(relationshipProps.classFullName)) {
473
- // replace standard insert error with something more helpful
474
- const errorMessage = `Relationship class "${relationshipProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
475
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
601
+ core_bentley_1.ITwinError.throwError({
602
+ iTwinErrorId: {
603
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
604
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
605
+ },
606
+ message: `Relationship class "${relationshipProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
607
+ cause: error,
608
+ });
476
609
  }
477
- throw error; // throw original error
610
+ throw error;
478
611
  }
479
612
  }
480
613
  /** Update an existing Relationship in the target iModel from the specified RelationshipProps.
481
614
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateRelationship`.
482
615
  */
483
- onUpdateRelationship(relationshipProps) {
616
+ async onUpdateRelationship(relationshipProps) {
484
617
  if (!relationshipProps.id) {
485
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Relationship instance Id not provided");
618
+ core_bentley_1.ITwinError.throwError({
619
+ iTwinErrorId: {
620
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
621
+ key: IModelTransformerError_1.IModelTransformerError.RelationshipIdRequired,
622
+ },
623
+ message: "Relationship instance Id not provided",
624
+ });
486
625
  }
487
- this.targetDb.relationships.updateInstance(relationshipProps);
626
+ this._editTxn.updateRelationship(relationshipProps);
488
627
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatRelationshipForLogger(relationshipProps)}`);
489
- this.trackProgress();
628
+ await this.trackProgress();
490
629
  }
491
630
  /** Delete the specified Relationship from the target iModel. */
492
- onDeleteRelationship(relationshipProps) {
631
+ async onDeleteRelationship(relationshipProps) {
493
632
  // Only passing in what deleteInstance actually uses, full relationshipProps is not necessary.
494
- this.targetDb.relationships.deleteInstance({
633
+ this._editTxn.deleteRelationship({
495
634
  id: relationshipProps.id,
496
635
  classFullName: relationshipProps.classFullName,
497
636
  });
498
637
  core_bentley_1.Logger.logInfo(loggerCategory, `Deleted relationship ${relationshipProps.classFullName} id=${relationshipProps.id}`);
499
- this.trackProgress();
638
+ await this.trackProgress();
500
639
  }
501
640
  /** Delete the specified Relationship from the target iModel. */
502
- deleteRelationship(relationshipProps) {
503
- this.onDeleteRelationship(relationshipProps);
641
+ async deleteRelationship(relationshipProps) {
642
+ await this.onDeleteRelationship(relationshipProps);
504
643
  }
505
644
  /** Format a Relationship for the Logger. */
506
645
  formatRelationshipForLogger(relProps) {
507
646
  return `${relProps.classFullName} sourceId=[${relProps.sourceId}] targetId=[${relProps.targetId}]`;
508
647
  }
509
648
  /** Tracks incremental progress */
510
- trackProgress() {
511
- this._progressCounter++;
512
- if (0 === this._progressCounter % this.progressInterval) {
513
- this.onProgress();
514
- }
649
+ async trackProgress(increment = 1) {
650
+ const previousProgressCounter = this._progressCounter;
651
+ this._progressCounter += increment;
652
+ const progressCallbacks = Math.floor(this._progressCounter / this.progressInterval) -
653
+ Math.floor(previousProgressCounter / this.progressInterval);
654
+ for (let i = 0; i < progressCallbacks; ++i)
655
+ await this.onProgress();
515
656
  }
516
657
  /** This method is called when IModelImporter has made incremental progress based on the [[progressInterval]] setting.
517
658
  * @note A subclass may override this method to report custom progress but should call `super.onProgress`.
518
659
  */
519
- onProgress() { }
660
+ async onProgress() { }
520
661
  /** Optionally compute the projectExtents for the target iModel depending on the options for this IModelImporter.
521
662
  * @note This method is automatically called from [IModelTransformer.process]($transformer).
522
663
  * @see [IModelDb.computeProjectExtents]($backend), [[autoExtendProjectExtents]]
@@ -533,13 +674,15 @@ class IModelImporter {
533
674
  const excludeOutliers = typeof this.options.autoExtendProjectExtents === "object"
534
675
  ? this.options.autoExtendProjectExtents.excludeOutliers
535
676
  : false;
536
- // If excludeOutliers is true, use the extents without outliers. If extentsWithOutliers is undefined, fall back to extents.
537
- const newProjectExtents = excludeOutliers
538
- ? computedProjectExtents.extents
539
- : (computedProjectExtents.extentsWithOutliers ??
540
- computedProjectExtents.extents);
677
+ // If excludeOutliers is true, use the extents without outliers.
678
+ // Otherwise prefer extentsWithOutliers, but fall back to extents if it's undefined or a null range.
679
+ const extentsWithOutliers = computedProjectExtents.extentsWithOutliers;
680
+ const useExtentsWithOutliers = !excludeOutliers && extentsWithOutliers && !extentsWithOutliers.isNull;
681
+ const newProjectExtents = useExtentsWithOutliers
682
+ ? extentsWithOutliers
683
+ : computedProjectExtents.extents;
541
684
  if (!newProjectExtents.isAlmostEqual(this.targetDb.projectExtents)) {
542
- this.targetDb.updateProjectExtents(newProjectExtents);
685
+ this._editTxn.updateProjectExtents(newProjectExtents);
543
686
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated projectExtents=${JSON.stringify(this.targetDb.projectExtents)}`);
544
687
  }
545
688
  if (!excludeOutliers &&
@@ -572,7 +715,7 @@ class IModelImporter {
572
715
  for (const [elementId, codeValue] of this._duplicateCodeValueMap) {
573
716
  const element = this.targetDb.elements.getElement(elementId);
574
717
  element.code.value = codeValue;
575
- element.update();
718
+ this._editTxn.updateElement(element.toJSON());
576
719
  }
577
720
  this._duplicateCodeValueMap.clear();
578
721
  }
@@ -580,7 +723,8 @@ class IModelImporter {
580
723
  * Needs to be called to perform necessary cleanup operations.
581
724
  * By not calling `finalize` there is a risk that data imported into targetDb will not be as expected.
582
725
  *
583
- * @note No need to call this If using [[IModelTransformer]] as it automatically invokes this method.
726
+ * @note [[IModelTransformer.process]] invokes this method automatically. Call it explicitly after
727
+ * composing the transformer's lower-level subset processing methods.
584
728
  */
585
729
  finalize() {
586
730
  this.resolveDuplicateCodeValues();
@@ -595,7 +739,11 @@ exports.IModelImporter = IModelImporter;
595
739
  */
596
740
  function hasEntityChanged(entity, entityProps, namesToIgnore) {
597
741
  let changed = false;
598
- entity.forEach((propertyName, property) => {
742
+ const entityClass = entity.iModel.schemaContext.getSchemaItemSync(entity.schemaItemKey);
743
+ (0, node_assert_1.strict)(ecschema_metadata_1.EntityClass.isEntityClass(entityClass) ||
744
+ ecschema_metadata_1.RelationshipClass.isRelationshipClass(entityClass), `Cannot get metadata for ${entity.classFullName}.`);
745
+ for (const property of entityClass.getPropertiesSync()) {
746
+ const propertyName = property.name;
599
747
  if (!changed) {
600
748
  if (namesToIgnore && namesToIgnore.has(propertyName)) {
601
749
  // skip
@@ -610,7 +758,7 @@ function hasEntityChanged(entity, entityProps, namesToIgnore) {
610
758
  changed = hasValueChanged(entity.asAny[core_common_1.ECJsNames.toJsName(propertyName)], entityProps[core_common_1.ECJsNames.toJsName(propertyName)]);
611
759
  }
612
760
  }
613
- });
761
+ }
614
762
  return changed;
615
763
  }
616
764
  /** Returns true if the specified binary values are different. */
@@ -640,9 +788,21 @@ function isDefaultSubCategory(props) {
640
788
  if (props.id === undefined)
641
789
  return false;
642
790
  if (!core_bentley_1.Id64.isId64(props.id))
643
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "subcategory had invalid id");
791
+ core_bentley_1.ITwinError.throwError({
792
+ iTwinErrorId: {
793
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
794
+ key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
795
+ },
796
+ message: "subcategory had invalid id",
797
+ });
644
798
  if (props.parent?.id === undefined)
645
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, `subcategory with id ${props.id} had no parent`);
799
+ core_bentley_1.ITwinError.throwError({
800
+ iTwinErrorId: {
801
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
802
+ key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
803
+ },
804
+ message: `subcategory with id ${props.id} had no parent`,
805
+ });
646
806
  return props.id === core_backend_1.IModelDb.getDefaultSubCategoryId(props.parent.id);
647
807
  }
648
808
  //# sourceMappingURL=IModelImporter.js.map