@itwin/imodel-transformer 2.0.0-dev.9 → 2.0.0-experimental.1

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 (128) 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 +61 -0
  16. package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
  17. package/lib/cjs/ChangesetScanner.js +104 -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 +54 -102
  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 +56 -0
  26. package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
  27. package/lib/cjs/ElementAspectCleanup.js +175 -0
  28. package/lib/cjs/ElementAspectCleanup.js.map +1 -0
  29. package/lib/cjs/ElementAspectExportCoordinator.d.ts +47 -0
  30. package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
  31. package/lib/cjs/ElementAspectExportCoordinator.js +160 -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 -32
  48. package/lib/cjs/EntityUnifier.js.map +1 -1
  49. package/lib/cjs/IModelCloneContext.d.ts +8 -2
  50. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  51. package/lib/cjs/IModelCloneContext.js +42 -12
  52. package/lib/cjs/IModelCloneContext.js.map +1 -1
  53. package/lib/cjs/IModelExporter.d.ts +143 -30
  54. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  55. package/lib/cjs/IModelExporter.js +871 -172
  56. package/lib/cjs/IModelExporter.js.map +1 -1
  57. package/lib/cjs/IModelImporter.d.ts +87 -31
  58. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  59. package/lib/cjs/IModelImporter.js +361 -139
  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 +132 -174
  66. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  67. package/lib/cjs/IModelTransformer.js +679 -1025
  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 +195 -0
  74. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  75. package/lib/cjs/ProvenanceManager.js +771 -0
  76. package/lib/cjs/ProvenanceManager.js.map +1 -0
  77. package/lib/cjs/SyncTypeResolver.d.ts +32 -0
  78. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  79. package/lib/cjs/SyncTypeResolver.js +92 -0
  80. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  81. package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
  82. package/lib/cjs/TransformerLoggerCategory.js +1 -1
  83. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  84. package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
  85. package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
  86. package/lib/cjs/TransformerPackageMetadata.js +47 -0
  87. package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
  88. package/lib/cjs/imodel-transformer.d.ts +27 -0
  89. package/lib/cjs/imodel-transformer.d.ts.map +1 -1
  90. package/lib/cjs/imodel-transformer.js +61 -34
  91. package/lib/cjs/imodel-transformer.js.map +1 -1
  92. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
  93. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
  94. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
  95. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
  96. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
  97. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
  98. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
  99. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
  100. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
  101. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
  102. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
  103. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
  104. package/lib/cjs/schema-processing.d.ts +7 -0
  105. package/lib/cjs/schema-processing.d.ts.map +1 -0
  106. package/lib/cjs/schema-processing.js +27 -0
  107. package/lib/cjs/schema-processing.js.map +1 -0
  108. package/package.json +70 -57
  109. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
  110. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
  111. package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
  112. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
  113. package/lib/cjs/ElementCascadingDeleter.d.ts +0 -22
  114. package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
  115. package/lib/cjs/ElementCascadingDeleter.js +0 -57
  116. package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
  117. package/lib/cjs/EntityMap.d.ts +0 -26
  118. package/lib/cjs/EntityMap.d.ts.map +0 -1
  119. package/lib/cjs/EntityMap.js +0 -54
  120. package/lib/cjs/EntityMap.js.map +0 -1
  121. package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
  122. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
  123. package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
  124. package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
  125. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
  126. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
  127. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
  128. 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 that an owner batch did not reuse, 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();
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)
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
+ });
388
+ }
282
389
  }
283
- /** Delete the specified Element from the target iModel. */
284
- deleteElement(elementId) {
285
- if (this.doNotUpdateElement(elementId)) {
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
+ }
286
402
  core_bentley_1.Logger.logInfo(loggerCategory, `Do not delete target element ${elementId}`);
287
- return;
288
403
  }
289
- this.onDeleteElement(elementId);
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) {
@@ -309,25 +432,79 @@ class IModelImporter {
309
432
  : "";
310
433
  return `${elementProps.classFullName} ${namePiece}[${elementProps.id}]`;
311
434
  }
312
- /** Import an ElementUniqueAspect into the target iModel. */
313
- importElementUniqueAspect(aspectProps) {
314
- const aspects = this.targetDb.elements.getAspects(aspectProps.element.id, aspectProps.classFullName);
315
- if (aspects.length === 0) {
435
+ /** Import an ElementUniqueAspect into the target iModel.
436
+ * @note Only an existing aspect of exactly `aspectProps.classFullName` on the owner is reused, and it is updated only when its properties differ.
437
+ * If there is none, the owner's unique aspects of a base or derived class are deleted through [[onDeleteElementAspect]] before the aspect is inserted, because iModel unique-aspect writes treat those classes as one slot.
438
+ * @returns the ID of the reused or inserted aspect
439
+ */
440
+ async importElementUniqueAspect(aspectProps) {
441
+ const elementId = aspectProps.element.id;
442
+ const existing = this.getTargetAspects(elementId, aspectProps.classFullName)[0];
443
+ if (existing === undefined) {
444
+ // iModel unique-aspect writes treat base and derived classes as one slot:
445
+ // inserting replaces an aspect of a derived class, and deleting an aspect
446
+ // of a base class also deletes derived ones. Delete related aspects first
447
+ // so deletion hooks run and later deletions cannot remove the new aspect.
448
+ for (const aspectId of await this.queryRelatedUniqueAspectIds(elementId, aspectProps.classFullName)) {
449
+ this._elementAspectCleanup.retain(aspectId);
450
+ const aspect = (0, ElementAspectCleanup_1.tryGetAspect)(this.targetDb, aspectId);
451
+ if (aspect !== undefined)
452
+ await this.onDeleteElementAspect(aspect);
453
+ }
316
454
  return this.onInsertElementAspect(aspectProps);
317
455
  }
318
- else if (hasEntityChanged(aspects[0], aspectProps)) {
319
- aspectProps.id = aspects[0].id;
320
- this.onUpdateElementAspect(aspectProps);
456
+ this._elementAspectCleanup.retain(existing.id);
457
+ if (hasEntityChanged(existing, aspectProps)) {
458
+ aspectProps.id = existing.id;
459
+ await this.onUpdateElementAspect(aspectProps);
321
460
  }
322
- return aspects[0].id;
461
+ return existing.id;
462
+ }
463
+ /** Returns the element's target aspects of exactly `classFullName`, in ECInstanceId order.
464
+ * Inside a transformer owner batch these come from the batch's loaded aspects; otherwise from the target iModel.
465
+ */
466
+ getTargetAspects(elementId, classFullName) {
467
+ return (this._elementAspectCleanup.getAspects(elementId, classFullName) ??
468
+ // getAspects is polymorphic; keep only the exact class so aspects of a
469
+ // derived class are handled with their own class.
470
+ this.targetDb.elements
471
+ .getAspects(elementId, classFullName)
472
+ .filter((aspect) => (0, ElementAspectCleanup_1.isSameClass)(aspect, classFullName)));
473
+ }
474
+ /** Returns the element's unique aspects whose class is a base or derived class of `classFullName`, excluding that class itself. */
475
+ async queryRelatedUniqueAspectIds(elementId, classFullName) {
476
+ // Most owners have no unique aspect of another class; the loaded batch
477
+ // aspects answer that without a query.
478
+ const loaded = this._elementAspectCleanup.getAspects(elementId);
479
+ if (loaded !== undefined &&
480
+ !loaded.some((aspect) => aspect instanceof core_backend_1.ElementUniqueAspect &&
481
+ !(0, ElementAspectCleanup_1.isSameClass)(aspect, classFullName)))
482
+ return [];
483
+ const ids = [];
484
+ for await (const row of this.targetDb.createQueryReader(`SELECT aspect.ECInstanceId id FROM bis.ElementUniqueAspect aspect
485
+ WHERE aspect.Element.Id = :elementId
486
+ AND aspect.ECClassId <> ec_classid(:classFullName)
487
+ AND (
488
+ aspect.ECClassId IN (
489
+ SELECT SourceECInstanceId FROM meta.ClassHasAllBaseClasses
490
+ WHERE TargetECInstanceId = ec_classid(:classFullName))
491
+ OR aspect.ECClassId IN (
492
+ SELECT TargetECInstanceId FROM meta.ClassHasAllBaseClasses
493
+ WHERE SourceECInstanceId = ec_classid(:classFullName)))`, new core_common_1.QueryBinder()
494
+ .bindId("elementId", elementId)
495
+ .bindString("classFullName", classFullName.replace(".", ":")), { usePrimaryConn: true })) {
496
+ ids.push(row.id);
497
+ }
498
+ return ids;
323
499
  }
324
500
  /** Import the collection of ElementMultiAspects into the target iModel.
325
501
  * @param aspectPropsArray The ElementMultiAspects to import
326
502
  * @param filterFunc Optional filter func that is used to exclude target ElementMultiAspects that were added during iModel transformation from the update detection logic.
327
503
  * @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.
504
+ * @note Aspects are grouped by `classFullName`. Each group is matched in order only against existing target aspects of exactly that class: matches are updated when their properties differ, extra props are inserted, and extra target aspects of that class are deleted. Target aspects of other classes, including derived classes, are not changed.
328
505
  * @returns the array of ids of the resulting ElementMultiAspects, in the same order of the aspectPropsArray parameter
329
506
  */
330
- importElementMultiAspects(aspectPropsArray,
507
+ async importElementMultiAspects(aspectPropsArray,
331
508
  /** caller must use this to enforce any aspects added by IModelTransformer are not considered for update */
332
509
  filterFunc = () => true) {
333
510
  const result = new Array(aspectPropsArray.length).fill(undefined);
@@ -335,91 +512,104 @@ class IModelImporter {
335
512
  return result;
336
513
  }
337
514
  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);
515
+ const proposedAspectsByClass = new Map();
516
+ aspectPropsArray.forEach((props, index) => {
517
+ const proposedAspects = proposedAspectsByClass.get(props.classFullName) ?? [];
518
+ proposedAspects.push({ props, index });
519
+ proposedAspectsByClass.set(props.classFullName, proposedAspects);
342
520
  });
343
521
  // 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);
348
- const currentAspects = this.targetDb.elements
349
- .getAspects(elementId, aspectClassFullName)
522
+ for (const [aspectClassFullName, proposedAspects,] of proposedAspectsByClass) {
523
+ const currentAspects = this.getTargetAspects(elementId, aspectClassFullName)
350
524
  .map((props, index) => ({ props, index }))
351
525
  .filter(({ props }) => filterFunc(props));
526
+ for (const { props } of currentAspects)
527
+ this._elementAspectCleanup.retain(props.id);
352
528
  if (proposedAspects.length >= currentAspects.length) {
353
- proposedAspects.forEach(({ props, index: resultIndex }, index) => {
529
+ for (let index = 0; index < proposedAspects.length; index++) {
530
+ const { props, index: resultIndex } = proposedAspects[index];
354
531
  let id;
355
532
  if (index < currentAspects.length) {
356
533
  id = currentAspects[index].props.id;
357
534
  props.id = id;
358
535
  if (hasEntityChanged(currentAspects[index].props, props)) {
359
- this.onUpdateElementAspect(props);
536
+ await this.onUpdateElementAspect(props);
360
537
  }
361
538
  id = props.id;
362
539
  }
363
540
  else {
364
- id = this.onInsertElementAspect(props);
541
+ id = await this.onInsertElementAspect(props);
365
542
  }
366
543
  result[resultIndex] = id;
367
- });
544
+ }
368
545
  }
369
546
  else {
370
- currentAspects.forEach(({ props, index: resultIndex }, index) => {
371
- let id;
547
+ for (let index = 0; index < currentAspects.length; index++) {
548
+ const { props } = currentAspects[index];
372
549
  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);
550
+ const { props: proposedProps, index: resultIndex } = proposedAspects[index];
551
+ const id = props.id;
552
+ proposedProps.id = id;
553
+ if (hasEntityChanged(props, proposedProps)) {
554
+ await this.onUpdateElementAspect(proposedProps);
377
555
  }
378
556
  result[resultIndex] = id;
379
557
  }
380
558
  else {
381
- this.onDeleteElementAspect(props);
559
+ await this.onDeleteElementAspect(props);
382
560
  }
383
- });
561
+ }
384
562
  }
385
- });
386
- assert(result.every((r) => typeof r !== undefined));
563
+ }
564
+ (0, node_assert_1.strict)(result.every((r) => r !== undefined));
387
565
  return result;
388
566
  }
389
567
  /** Insert the ElementAspect into the target iModel.
390
568
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertElementAspect`.
391
569
  */
392
- onInsertElementAspect(aspectProps) {
570
+ async onInsertElementAspect(aspectProps) {
571
+ // Also covers overrides that write another owner's aspects through this hook.
572
+ this._elementAspectCleanup.beforeWrite(aspectProps.element.id);
393
573
  try {
394
- const id = this.targetDb.elements.insertAspect(aspectProps);
574
+ const id = this._editTxn.insertAspect(aspectProps);
575
+ this._elementAspectCleanup.afterWrite();
395
576
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementAspectForLogger(aspectProps)}`);
396
- this.trackProgress();
577
+ await this.trackProgress();
397
578
  return id;
398
579
  }
399
580
  catch (error) {
400
581
  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);
582
+ core_bentley_1.ITwinError.throwError({
583
+ iTwinErrorId: {
584
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
585
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
586
+ },
587
+ message: `ElementAspect class "${aspectProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
588
+ cause: error,
589
+ });
404
590
  }
405
- throw error; // throw original error
591
+ throw error;
406
592
  }
407
593
  }
408
594
  /** Update the ElementAspect within the target iModel.
409
595
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateElementAspect`.
410
596
  */
411
- onUpdateElementAspect(aspectProps) {
412
- this.targetDb.elements.updateAspect(aspectProps);
597
+ async onUpdateElementAspect(aspectProps) {
598
+ this._elementAspectCleanup.beforeWrite(aspectProps.element.id);
599
+ this._editTxn.updateAspect(aspectProps);
600
+ this._elementAspectCleanup.afterWrite();
413
601
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementAspectForLogger(aspectProps)}`);
414
- this.trackProgress();
602
+ await this.trackProgress();
415
603
  }
416
604
  /** Delete the specified ElementAspect from the target iModel.
417
605
  * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElementAspect`.
418
606
  */
419
- onDeleteElementAspect(targetElementAspect) {
420
- this.targetDb.elements.deleteAspect(targetElementAspect.id);
607
+ async onDeleteElementAspect(targetElementAspect) {
608
+ this._elementAspectCleanup.beforeWrite(targetElementAspect.element.id);
609
+ this._editTxn.deleteAspect(targetElementAspect.id);
610
+ this._elementAspectCleanup.afterWrite();
421
611
  core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${this.formatElementAspectForLogger(targetElementAspect)}`);
422
- this.trackProgress();
612
+ await this.trackProgress();
423
613
  }
424
614
  /** Format an ElementAspect for the Logger. */
425
615
  formatElementAspectForLogger(elementAspectProps) {
@@ -428,7 +618,7 @@ class IModelImporter {
428
618
  /** Import the specified RelationshipProps (either as an insert or an update) into the target iModel.
429
619
  * @returns The instance Id of the inserted or updated Relationship.
430
620
  */
431
- importRelationship(relationshipProps) {
621
+ async importRelationship(relationshipProps) {
432
622
  if (undefined === relationshipProps.sourceId ||
433
623
  !core_bentley_1.Id64.isValidId64(relationshipProps.sourceId)) {
434
624
  core_bentley_1.Logger.logInfo(loggerCategory, `Ignoring ${relationshipProps.classFullName} instance because of invalid RelationshipProps.sourceId`);
@@ -449,7 +639,7 @@ class IModelImporter {
449
639
  // if relationship found, update it
450
640
  relationshipProps.id = relationship.id;
451
641
  if (hasEntityChanged(relationship, relationshipProps)) {
452
- this.onUpdateRelationship(relationshipProps);
642
+ await this.onUpdateRelationship(relationshipProps);
453
643
  }
454
644
  return relationshipProps.id;
455
645
  }
@@ -461,62 +651,75 @@ class IModelImporter {
461
651
  * @returns The instance Id of the newly inserted relationship.
462
652
  * @note A subclass may override this method to customize insert behavior but should call `super.onInsertRelationship`.
463
653
  */
464
- onInsertRelationship(relationshipProps) {
654
+ async onInsertRelationship(relationshipProps) {
465
655
  try {
466
- const targetRelInstanceId = this.targetDb.relationships.insertInstance(relationshipProps);
656
+ const targetRelInstanceId = this._editTxn.insertRelationship(relationshipProps);
467
657
  core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatRelationshipForLogger(relationshipProps)}`);
468
- this.trackProgress();
658
+ await this.trackProgress();
469
659
  return targetRelInstanceId;
470
660
  }
471
661
  catch (error) {
472
662
  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);
663
+ core_bentley_1.ITwinError.throwError({
664
+ iTwinErrorId: {
665
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
666
+ key: IModelTransformerError_1.IModelTransformerError.TargetClassNotFound,
667
+ },
668
+ message: `Relationship class "${relationshipProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`,
669
+ cause: error,
670
+ });
476
671
  }
477
- throw error; // throw original error
672
+ throw error;
478
673
  }
479
674
  }
480
675
  /** Update an existing Relationship in the target iModel from the specified RelationshipProps.
481
676
  * @note A subclass may override this method to customize update behavior but should call `super.onUpdateRelationship`.
482
677
  */
483
- onUpdateRelationship(relationshipProps) {
678
+ async onUpdateRelationship(relationshipProps) {
484
679
  if (!relationshipProps.id) {
485
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Relationship instance Id not provided");
680
+ core_bentley_1.ITwinError.throwError({
681
+ iTwinErrorId: {
682
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
683
+ key: IModelTransformerError_1.IModelTransformerError.RelationshipIdRequired,
684
+ },
685
+ message: "Relationship instance Id not provided",
686
+ });
486
687
  }
487
- this.targetDb.relationships.updateInstance(relationshipProps);
688
+ this._editTxn.updateRelationship(relationshipProps);
488
689
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatRelationshipForLogger(relationshipProps)}`);
489
- this.trackProgress();
690
+ await this.trackProgress();
490
691
  }
491
692
  /** Delete the specified Relationship from the target iModel. */
492
- onDeleteRelationship(relationshipProps) {
693
+ async onDeleteRelationship(relationshipProps) {
493
694
  // Only passing in what deleteInstance actually uses, full relationshipProps is not necessary.
494
- this.targetDb.relationships.deleteInstance({
695
+ this._editTxn.deleteRelationship({
495
696
  id: relationshipProps.id,
496
697
  classFullName: relationshipProps.classFullName,
497
698
  });
498
699
  core_bentley_1.Logger.logInfo(loggerCategory, `Deleted relationship ${relationshipProps.classFullName} id=${relationshipProps.id}`);
499
- this.trackProgress();
700
+ await this.trackProgress();
500
701
  }
501
702
  /** Delete the specified Relationship from the target iModel. */
502
- deleteRelationship(relationshipProps) {
503
- this.onDeleteRelationship(relationshipProps);
703
+ async deleteRelationship(relationshipProps) {
704
+ await this.onDeleteRelationship(relationshipProps);
504
705
  }
505
706
  /** Format a Relationship for the Logger. */
506
707
  formatRelationshipForLogger(relProps) {
507
708
  return `${relProps.classFullName} sourceId=[${relProps.sourceId}] targetId=[${relProps.targetId}]`;
508
709
  }
509
710
  /** Tracks incremental progress */
510
- trackProgress() {
511
- this._progressCounter++;
512
- if (0 === this._progressCounter % this.progressInterval) {
513
- this.onProgress();
514
- }
711
+ async trackProgress(increment = 1) {
712
+ const previousProgressCounter = this._progressCounter;
713
+ this._progressCounter += increment;
714
+ const progressCallbacks = Math.floor(this._progressCounter / this.progressInterval) -
715
+ Math.floor(previousProgressCounter / this.progressInterval);
716
+ for (let i = 0; i < progressCallbacks; ++i)
717
+ await this.onProgress();
515
718
  }
516
719
  /** This method is called when IModelImporter has made incremental progress based on the [[progressInterval]] setting.
517
720
  * @note A subclass may override this method to report custom progress but should call `super.onProgress`.
518
721
  */
519
- onProgress() { }
722
+ async onProgress() { }
520
723
  /** Optionally compute the projectExtents for the target iModel depending on the options for this IModelImporter.
521
724
  * @note This method is automatically called from [IModelTransformer.process]($transformer).
522
725
  * @see [IModelDb.computeProjectExtents]($backend), [[autoExtendProjectExtents]]
@@ -533,13 +736,15 @@ class IModelImporter {
533
736
  const excludeOutliers = typeof this.options.autoExtendProjectExtents === "object"
534
737
  ? this.options.autoExtendProjectExtents.excludeOutliers
535
738
  : 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);
739
+ // If excludeOutliers is true, use the extents without outliers.
740
+ // Otherwise prefer extentsWithOutliers, but fall back to extents if it's undefined or a null range.
741
+ const extentsWithOutliers = computedProjectExtents.extentsWithOutliers;
742
+ const useExtentsWithOutliers = !excludeOutliers && extentsWithOutliers && !extentsWithOutliers.isNull;
743
+ const newProjectExtents = useExtentsWithOutliers
744
+ ? extentsWithOutliers
745
+ : computedProjectExtents.extents;
541
746
  if (!newProjectExtents.isAlmostEqual(this.targetDb.projectExtents)) {
542
- this.targetDb.updateProjectExtents(newProjectExtents);
747
+ this._editTxn.updateProjectExtents(newProjectExtents);
543
748
  core_bentley_1.Logger.logInfo(loggerCategory, `Updated projectExtents=${JSON.stringify(this.targetDb.projectExtents)}`);
544
749
  }
545
750
  if (!excludeOutliers &&
@@ -572,7 +777,7 @@ class IModelImporter {
572
777
  for (const [elementId, codeValue] of this._duplicateCodeValueMap) {
573
778
  const element = this.targetDb.elements.getElement(elementId);
574
779
  element.code.value = codeValue;
575
- element.update();
780
+ this._editTxn.updateElement(element.toJSON());
576
781
  }
577
782
  this._duplicateCodeValueMap.clear();
578
783
  }
@@ -580,7 +785,8 @@ class IModelImporter {
580
785
  * Needs to be called to perform necessary cleanup operations.
581
786
  * By not calling `finalize` there is a risk that data imported into targetDb will not be as expected.
582
787
  *
583
- * @note No need to call this If using [[IModelTransformer]] as it automatically invokes this method.
788
+ * @note [[IModelTransformer.process]] invokes this method automatically. Call it explicitly after
789
+ * composing the transformer's lower-level subset processing methods.
584
790
  */
585
791
  finalize() {
586
792
  this.resolveDuplicateCodeValues();
@@ -595,7 +801,11 @@ exports.IModelImporter = IModelImporter;
595
801
  */
596
802
  function hasEntityChanged(entity, entityProps, namesToIgnore) {
597
803
  let changed = false;
598
- entity.forEach((propertyName, property) => {
804
+ const entityClass = entity.iModel.schemaContext.getSchemaItemSync(entity.schemaItemKey);
805
+ (0, node_assert_1.strict)(ecschema_metadata_1.EntityClass.isEntityClass(entityClass) ||
806
+ ecschema_metadata_1.RelationshipClass.isRelationshipClass(entityClass), `Cannot get metadata for ${entity.classFullName}.`);
807
+ for (const property of entityClass.getPropertiesSync()) {
808
+ const propertyName = property.name;
599
809
  if (!changed) {
600
810
  if (namesToIgnore && namesToIgnore.has(propertyName)) {
601
811
  // skip
@@ -610,7 +820,7 @@ function hasEntityChanged(entity, entityProps, namesToIgnore) {
610
820
  changed = hasValueChanged(entity.asAny[core_common_1.ECJsNames.toJsName(propertyName)], entityProps[core_common_1.ECJsNames.toJsName(propertyName)]);
611
821
  }
612
822
  }
613
- });
823
+ }
614
824
  return changed;
615
825
  }
616
826
  /** Returns true if the specified binary values are different. */
@@ -640,9 +850,21 @@ function isDefaultSubCategory(props) {
640
850
  if (props.id === undefined)
641
851
  return false;
642
852
  if (!core_bentley_1.Id64.isId64(props.id))
643
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, "subcategory had invalid id");
853
+ core_bentley_1.ITwinError.throwError({
854
+ iTwinErrorId: {
855
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
856
+ key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
857
+ },
858
+ message: "subcategory had invalid id",
859
+ });
644
860
  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`);
861
+ core_bentley_1.ITwinError.throwError({
862
+ iTwinErrorId: {
863
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
864
+ key: IModelTransformerError_1.IModelTransformerError.InvalidSubCategory,
865
+ },
866
+ message: `subcategory with id ${props.id} had no parent`,
867
+ });
646
868
  return props.id === core_backend_1.IModelDb.getDefaultSubCategoryId(props.parent.id);
647
869
  }
648
870
  //# sourceMappingURL=IModelImporter.js.map