@itwin/imodel-transformer 2.0.0-dev.8 → 2.0.0-experimental.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 -24
  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 +194 -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
@@ -0,0 +1,771 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProvenanceManager = void 0;
4
+ exports.queryTargetScopeElementIds = queryTargetScopeElementIds;
5
+ exports.isTransformerProvenanceAspect = isTransformerProvenanceAspect;
6
+ exports.sourceProvenanceMatchFilter = sourceProvenanceMatchFilter;
7
+ /*---------------------------------------------------------------------------------------------
8
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
+ * See LICENSE.md in the project root for license terms and full copyright notice.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ const core_bentley_1 = require("@itwin/core-bentley");
12
+ const core_backend_1 = require("@itwin/core-backend");
13
+ const core_common_1 = require("@itwin/core-common");
14
+ const ElementAspectCleanup_1 = require("./ElementAspectCleanup");
15
+ const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
16
+ const IModelTransformerError_1 = require("./IModelTransformerError");
17
+ const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelTransformer;
18
+ /**
19
+ * Manages provenance scope aspects and synchronization versioning.
20
+ * Encapsulates all ESA scope management, sync version tracking, and
21
+ * provenance DB direction logic extracted from IModelTransformer.
22
+ * @internal
23
+ */
24
+ class ProvenanceManager {
25
+ _context;
26
+ _targetScopeElementId;
27
+ _transformerOptions;
28
+ _syncTypeResolver;
29
+ _startingChangesetIndices;
30
+ /** NOTE: the json properties must be converted to string before insertion */
31
+ _targetScopeProvenanceProps = undefined;
32
+ _cachedSynchronizationVersion = undefined;
33
+ _targetClassNameToClassIdCache = new Map();
34
+ _targetEditTxn;
35
+ _sourceEditTxn;
36
+ constructor(targetScopeElementId, transformerOptions, context, syncTypeResolver, targetEditTxn, sourceEditTxn) {
37
+ this._targetScopeElementId = targetScopeElementId;
38
+ this._transformerOptions = transformerOptions;
39
+ this._context = context;
40
+ this._syncTypeResolver = syncTypeResolver;
41
+ this._targetEditTxn = targetEditTxn;
42
+ this._sourceEditTxn = sourceEditTxn;
43
+ const sourceDb = this._context.sourceDb;
44
+ const targetDb = this._context.targetDb;
45
+ if (sourceDb.isBriefcase && targetDb.isBriefcase) {
46
+ if (sourceDb.changeset.index === undefined ||
47
+ targetDb.changeset.index === undefined)
48
+ core_bentley_1.ITwinError.throwError({
49
+ iTwinErrorId: {
50
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
51
+ key: IModelTransformerError_1.IModelTransformerError.ChangesetIndexUnavailable,
52
+ },
53
+ message: "database has no changeset index",
54
+ });
55
+ this._startingChangesetIndices = {
56
+ target: targetDb.changeset.index,
57
+ source: sourceDb.changeset.index,
58
+ };
59
+ }
60
+ }
61
+ async _isReverseSynchronization() {
62
+ return (await this._syncTypeResolver.getSyncType()) === "reverse";
63
+ }
64
+ async _queryTargetRelId(sourceRelInfo) {
65
+ const targetRelInfo = {
66
+ sourceId: this._context.findTargetElementId(sourceRelInfo.sourceId),
67
+ targetId: this._context.findTargetElementId(sourceRelInfo.targetId),
68
+ };
69
+ if (targetRelInfo.sourceId === undefined ||
70
+ targetRelInfo.targetId === undefined)
71
+ return undefined;
72
+ const sql = `
73
+ select ecinstanceid
74
+ from bis.elementreferstoelements
75
+ where sourceecinstanceid=?
76
+ and targetecinstanceid=?
77
+ and ecclassid=?
78
+ `;
79
+ const params = new core_common_1.QueryBinder();
80
+ params.bindId(1, targetRelInfo.sourceId);
81
+ params.bindId(2, targetRelInfo.targetId);
82
+ params.bindId(3, await this._targetClassNameToClassId(sourceRelInfo.classFullName));
83
+ const result = this._context.targetDb.createQueryReader(sql, params, {
84
+ usePrimaryConn: true,
85
+ });
86
+ if (await result.step())
87
+ return result.current.id;
88
+ else
89
+ return undefined;
90
+ }
91
+ async _targetClassNameToClassId(classFullName) {
92
+ let classId = this._targetClassNameToClassIdCache.get(classFullName);
93
+ if (classId === undefined) {
94
+ classId = await this._getRelClassId(this._context.targetDb, classFullName);
95
+ this._targetClassNameToClassIdCache.set(classFullName, classId);
96
+ }
97
+ return classId;
98
+ }
99
+ async _getRelClassId(db, classFullName) {
100
+ const sql = `
101
+ SELECT c.ECInstanceId
102
+ FROM ECDbMeta.ECClassDef c
103
+ JOIN ECDbMeta.ECSchemaDef s ON c.Schema.Id=s.ECInstanceId
104
+ WHERE s.Name=? AND c.Name=?
105
+ `;
106
+ const [schemaName, className] = classFullName.indexOf(".") !== -1
107
+ ? classFullName.split(".")
108
+ : classFullName.split(":");
109
+ const params = new core_common_1.QueryBinder();
110
+ params.bindString(1, schemaName);
111
+ params.bindString(2, className);
112
+ const result = db.createQueryReader(sql, params, { usePrimaryConn: true });
113
+ if (await result.step())
114
+ return result.current.id;
115
+ core_bentley_1.ITwinError.throwError({
116
+ iTwinErrorId: {
117
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
118
+ key: IModelTransformerError_1.IModelTransformerError.RelationshipClassNotFound,
119
+ },
120
+ message: `Could not find class ${classFullName} in the db`,
121
+ });
122
+ }
123
+ // ── Static provenance metadata ──────────────────────────────────────────
124
+ /** The element classes that are considered to define provenance in the iModel */
125
+ static get provenanceElementClasses() {
126
+ return [
127
+ core_backend_1.FolderLink,
128
+ core_backend_1.SynchronizationConfigLink,
129
+ core_backend_1.ExternalSource,
130
+ core_backend_1.ExternalSourceAttachment,
131
+ ];
132
+ }
133
+ /** The element aspect classes that are considered to define provenance in the iModel */
134
+ static get provenanceElementAspectClasses() {
135
+ return [core_backend_1.ExternalSourceAspect];
136
+ }
137
+ // ── Static provenance queries ──────────────────────────────────────────
138
+ /**
139
+ * Iterate all matching federation guids and ExternalSourceAspects in the provenance iModel (target unless reverse sync)
140
+ * and call a function for each one.
141
+ * @note provenance is done by federation guids where possible
142
+ * @note this may execute on each element more than once! Only use in cases where that is handled
143
+ */
144
+ static async forEachTrackedElement(args) {
145
+ if (args.provenanceDb === args.provenanceSourceDb)
146
+ return;
147
+ if (!args.provenanceDb.containsClass(core_backend_1.ExternalSourceAspect.classFullName)) {
148
+ core_bentley_1.ITwinError.throwError({
149
+ iTwinErrorId: {
150
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
151
+ key: IModelTransformerError_1.IModelTransformerError.ProvenanceSchemaUnsupported,
152
+ },
153
+ message: "The BisCore schema version of the target database is too old",
154
+ });
155
+ }
156
+ const sourceDb = args.isReverseSynchronization
157
+ ? args.provenanceDb
158
+ : args.provenanceSourceDb;
159
+ const targetDb = args.isReverseSynchronization
160
+ ? args.provenanceSourceDb
161
+ : args.provenanceDb;
162
+ // query for provenanceDb
163
+ const elementIdByFedGuidQuery = `
164
+ SELECT e.ECInstanceId, FederationGuid
165
+ FROM bis.Element e
166
+ ${args.skipPropagateChangesToRootElements
167
+ ? "WHERE e.ECInstanceId NOT IN (0x1, 0xe, 0x10) -- special static elements"
168
+ : ""}
169
+ ORDER BY FederationGuid
170
+ `;
171
+ // iterate through sorted list of fed guids from both dbs to get the intersection
172
+ // NOTE: if we exposed the native attach database support,
173
+ // we could get the intersection of fed guids in one query, not sure if it would be faster
174
+ // OR we could do a raw sqlite query...
175
+ const sourceReader = sourceDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
176
+ const targetReader = targetDb.createQueryReader(elementIdByFedGuidQuery, undefined, { usePrimaryConn: true });
177
+ let hasSourceRow = await sourceReader.step();
178
+ let hasTargetRow = await targetReader.step();
179
+ while (hasSourceRow && hasTargetRow) {
180
+ const sourceFedGuid = sourceReader.current.federationGuid;
181
+ const targetFedGuid = targetReader.current.federationGuid;
182
+ if (sourceFedGuid !== undefined &&
183
+ targetFedGuid !== undefined &&
184
+ sourceFedGuid === targetFedGuid) {
185
+ // data flow direction is always sourceDb -> targetDb and it does not depend on where the explicit element provenance is stored
186
+ args.fn(sourceReader.current.id, targetReader.current.id);
187
+ }
188
+ if (targetFedGuid === undefined ||
189
+ (sourceFedGuid !== undefined && sourceFedGuid >= targetFedGuid)) {
190
+ hasTargetRow = await targetReader.step();
191
+ }
192
+ if (sourceFedGuid === undefined ||
193
+ (targetFedGuid !== undefined && sourceFedGuid <= targetFedGuid)) {
194
+ hasSourceRow = await sourceReader.step();
195
+ }
196
+ }
197
+ // query for provenanceDb
198
+ const provenanceAspectsQuery = `
199
+ SELECT esa.Identifier, Element.Id
200
+ FROM bis.ExternalSourceAspect esa
201
+ WHERE Scope.Id=:scopeId
202
+ AND Kind=:kind
203
+ ORDER BY esa.Identifier, esa.ECInstanceId DESC
204
+ `;
205
+ // Technically this will a second time call the function (as documented) on
206
+ // victims of the old provenance method that have both fedguids and an inserted aspect.
207
+ // But this is a private function with one known caller where that doesn't matter
208
+ const runFnInDataFlowDirection = (sourceId, targetId) => args.isReverseSynchronization
209
+ ? args.fn(sourceId, targetId)
210
+ : args.fn(targetId, sourceId);
211
+ const params = new core_common_1.QueryBinder();
212
+ params.bindId("scopeId", args.targetScopeElementId);
213
+ params.bindString("kind", core_backend_1.ExternalSourceAspect.Kind.Element);
214
+ const provenanceReader = args.provenanceDb.createQueryReader(provenanceAspectsQuery, params, { usePrimaryConn: true });
215
+ for await (const row of provenanceReader) {
216
+ // ExternalSourceAspect.Identifier is of type string
217
+ const aspectIdentifier = row[0];
218
+ const elementId = row.id;
219
+ runFnInDataFlowDirection(elementId, aspectIdentifier);
220
+ }
221
+ }
222
+ /**
223
+ * Queries for an ESA which matches the props in the provided aspectProps.
224
+ * @param dbToQuery db to run the query on
225
+ * @param aspectProps aspectProps to search for
226
+ */
227
+ static async queryScopeExternalSourceAspect(dbToQuery, aspectProps) {
228
+ const sql = `
229
+ SELECT ECInstanceId, Version, JsonProperties
230
+ FROM ${core_backend_1.ExternalSourceAspect.classFullName}
231
+ WHERE Element.Id=:elementId
232
+ AND Scope.Id=:scopeId
233
+ AND Kind=:kind
234
+ AND Identifier=:identifier
235
+ LIMIT 1
236
+ `;
237
+ if (aspectProps.scope === undefined)
238
+ return undefined;
239
+ const params = new core_common_1.QueryBinder()
240
+ .bindId("elementId", aspectProps.element.id)
241
+ .bindId("scopeId", aspectProps.scope.id)
242
+ .bindString("kind", aspectProps.kind)
243
+ .bindString("identifier", aspectProps.identifier);
244
+ return dbToQuery.withQueryReader(sql, (reader) => {
245
+ if (!reader.step())
246
+ return undefined;
247
+ const aspectId = reader.current[0];
248
+ const version = reader.current[1];
249
+ const jsonProperties = reader.current[2];
250
+ return { aspectId, version, jsonProperties };
251
+ }, params);
252
+ }
253
+ // ── Provenance DB direction ────────────────────────────────────────────
254
+ /** Return the IModelDb where provenance is stored (for reads/queries).
255
+ * This will be targetDb except when it is a reverse synchronization, in which case it will be sourceDb.
256
+ */
257
+ async getProvenanceDb() {
258
+ return (await this._isReverseSynchronization())
259
+ ? this._context.sourceDb
260
+ : this._context.targetDb;
261
+ }
262
+ /** Return the EditTxn for writing provenance.
263
+ * This will be targetEditTxn except when it is a reverse synchronization, in which case it will be sourceEditTxn.
264
+ */
265
+ async getProvenanceEditTxn() {
266
+ if (await this._isReverseSynchronization()) {
267
+ if (!this._sourceEditTxn) {
268
+ core_bentley_1.ITwinError.throwError({
269
+ iTwinErrorId: {
270
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
271
+ key: IModelTransformerError_1.IModelTransformerError.SourceEditTxnRequired,
272
+ },
273
+ message: "A reverse synchronization requires a sourceEditTxn to write provenance back to the source iModel. " +
274
+ "Pass sourceEditTxn in IModelTransformOptions.",
275
+ });
276
+ }
277
+ return this._sourceEditTxn;
278
+ }
279
+ return this._targetEditTxn;
280
+ }
281
+ /** Return the IModelDb where entities referred to by stored provenance live.
282
+ * This will be sourceDb except when it is a reverse synchronization, in which case it will be targetDb.
283
+ */
284
+ async getProvenanceSourceDb() {
285
+ return (await this._isReverseSynchronization())
286
+ ? this._context.targetDb
287
+ : this._context.sourceDb;
288
+ }
289
+ // ── Scope aspect management ────────────────────────────────────────────
290
+ /**
291
+ * @returns provenance scope aspect if it exists in the provenanceDb.
292
+ * Provenance scope aspect is created and inserted into provenanceDb when [[initScopeProvenance]] is invoked.
293
+ */
294
+ async tryGetProvenanceScopeAspect() {
295
+ const scopeProvenanceAspectProps = await ProvenanceManager.queryScopeExternalSourceAspect(await this.getProvenanceDb(), {
296
+ id: undefined,
297
+ classFullName: core_backend_1.ExternalSourceAspect.classFullName,
298
+ scope: { id: core_common_1.IModel.rootSubjectId },
299
+ kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
300
+ element: {
301
+ id: this._targetScopeElementId ?? core_common_1.IModel.rootSubjectId,
302
+ },
303
+ identifier: (await this.getProvenanceSourceDb()).iModelId,
304
+ });
305
+ return scopeProvenanceAspectProps !== undefined
306
+ ? (await this.getProvenanceDb()).elements.getAspect(scopeProvenanceAspectProps.aspectId)
307
+ : undefined;
308
+ }
309
+ /**
310
+ * Make sure there are no conflicting other scope-type external source aspects on the target scope element.
311
+ * If there are none at all, insert one (this must be a first synchronization).
312
+ */
313
+ async initScopeProvenance() {
314
+ const provenanceDb = await this.getProvenanceDb();
315
+ const provenanceEditTxn = await this.getProvenanceEditTxn();
316
+ const sourceProvenanceDb = await this.getProvenanceSourceDb();
317
+ const aspectProps = {
318
+ id: undefined,
319
+ version: undefined,
320
+ classFullName: core_backend_1.ExternalSourceAspect.classFullName,
321
+ element: {
322
+ id: this._targetScopeElementId,
323
+ relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
324
+ },
325
+ scope: { id: core_common_1.IModel.rootSubjectId },
326
+ identifier: sourceProvenanceDb.iModelId,
327
+ kind: core_backend_1.ExternalSourceAspect.Kind.Scope,
328
+ jsonProperties: undefined,
329
+ };
330
+ const foundEsaProps = await ProvenanceManager.queryScopeExternalSourceAspect(provenanceDb, aspectProps);
331
+ if (foundEsaProps === undefined) {
332
+ aspectProps.version = "";
333
+ aspectProps.jsonProperties = {
334
+ pendingReverseSyncChangesetIndices: [],
335
+ pendingSyncChangesetIndices: [],
336
+ reverseSyncVersion: "",
337
+ };
338
+ // query without "identifier" to find possible conflicts
339
+ const sql = `
340
+ SELECT ECInstanceId
341
+ FROM ${core_backend_1.ExternalSourceAspect.classFullName}
342
+ WHERE Element.Id=:elementId
343
+ AND Scope.Id=:scopeId
344
+ AND Kind=:kind
345
+ LIMIT 1
346
+ `;
347
+ const params = new core_common_1.QueryBinder();
348
+ params.bindId("elementId", aspectProps.element.id);
349
+ params.bindId("scopeId", aspectProps.scope.id);
350
+ params.bindString("kind", aspectProps.kind);
351
+ const reader = provenanceDb.createQueryReader(sql, params, {
352
+ usePrimaryConn: true,
353
+ });
354
+ const hasConflictingScope = await reader.step();
355
+ if (hasConflictingScope) {
356
+ core_bentley_1.ITwinError.throwError({
357
+ iTwinErrorId: {
358
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
359
+ key: IModelTransformerError_1.IModelTransformerError.ProvenanceScopeConflict,
360
+ },
361
+ message: "Provenance scope conflict",
362
+ });
363
+ }
364
+ if (!this._transformerOptions.noProvenance) {
365
+ const id = provenanceEditTxn.insertAspect({
366
+ ...aspectProps,
367
+ jsonProperties: JSON.stringify(aspectProps.jsonProperties),
368
+ });
369
+ aspectProps.id = id;
370
+ this.clearCachedSynchronizationVersion();
371
+ }
372
+ }
373
+ else {
374
+ aspectProps.id = foundEsaProps.aspectId;
375
+ aspectProps.version = foundEsaProps.version;
376
+ aspectProps.jsonProperties = foundEsaProps.jsonProperties
377
+ ? JSON.parse(foundEsaProps.jsonProperties)
378
+ : undefined;
379
+ const oldProps = JSON.parse(JSON.stringify(aspectProps));
380
+ if (this.handleUnsafeMigrate(aspectProps)) {
381
+ core_bentley_1.Logger.logInfo(loggerCategory, "Unsafe migrate made a change to the target scope's external source aspect. Updating aspect in database.", { oldProps, newProps: aspectProps });
382
+ provenanceEditTxn.updateAspect({
383
+ ...aspectProps,
384
+ jsonProperties: JSON.stringify(aspectProps.jsonProperties),
385
+ });
386
+ this.clearCachedSynchronizationVersion();
387
+ }
388
+ }
389
+ this._targetScopeProvenanceProps =
390
+ aspectProps;
391
+ }
392
+ /** Returns true if a change was made to the aspectProps. */
393
+ handleUnsafeMigrate(aspectProps) {
394
+ let madeChange = false;
395
+ if (this._transformerOptions.branchRelationshipDataBehavior !==
396
+ "unsafe-migrate")
397
+ return madeChange;
398
+ const fallbackSyncVersionToUse = this._transformerOptions.argsForProcessChanges
399
+ ?.unsafeFallbackSyncVersion ?? "";
400
+ const fallbackReverseSyncVersionToUse = this._transformerOptions.argsForProcessChanges
401
+ ?.unsafeFallbackReverseSyncVersion ?? "";
402
+ if (aspectProps.version === undefined ||
403
+ (aspectProps.version === "" &&
404
+ aspectProps.version !== fallbackSyncVersionToUse)) {
405
+ aspectProps.version = fallbackSyncVersionToUse;
406
+ madeChange = true;
407
+ }
408
+ if (aspectProps.jsonProperties === undefined) {
409
+ aspectProps.jsonProperties = {
410
+ pendingReverseSyncChangesetIndices: [],
411
+ pendingSyncChangesetIndices: [],
412
+ reverseSyncVersion: fallbackReverseSyncVersionToUse,
413
+ };
414
+ madeChange = true;
415
+ }
416
+ else if (aspectProps.jsonProperties.reverseSyncVersion === undefined ||
417
+ (aspectProps.jsonProperties.reverseSyncVersion === "" &&
418
+ aspectProps.jsonProperties.reverseSyncVersion !==
419
+ fallbackReverseSyncVersionToUse)) {
420
+ aspectProps.jsonProperties.reverseSyncVersion =
421
+ fallbackReverseSyncVersionToUse;
422
+ madeChange = true;
423
+ }
424
+ if (aspectProps.jsonProperties.pendingReverseSyncChangesetIndices ===
425
+ undefined) {
426
+ core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingReverseSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
427
+ aspectProps.jsonProperties.pendingReverseSyncChangesetIndices = [];
428
+ madeChange = true;
429
+ }
430
+ if (aspectProps.jsonProperties.pendingSyncChangesetIndices === undefined) {
431
+ core_bentley_1.Logger.logWarning(loggerCategory, "Property pendingSyncChangesetIndices missing on the jsonProperties of the scoping ESA. Setting to [].");
432
+ aspectProps.jsonProperties.pendingSyncChangesetIndices = [];
433
+ madeChange = true;
434
+ }
435
+ return madeChange;
436
+ }
437
+ // ── Synchronization version ────────────────────────────────────────────
438
+ /**
439
+ * We cache the synchronization version to avoid querying the target scoping ESA multiple times.
440
+ * Clears the cached value so the next call to getSynchronizationVersion re-queries.
441
+ */
442
+ clearCachedSynchronizationVersion() {
443
+ this._cachedSynchronizationVersion = undefined;
444
+ }
445
+ /** The changeset version in the scoping element's source version found for this transformation.
446
+ * @note the version depends on whether this is a reverse synchronization or not.
447
+ * @note empty string and -1 for changeset and index if it has never been transformed.
448
+ */
449
+ async getSynchronizationVersion() {
450
+ if (this._cachedSynchronizationVersion === undefined) {
451
+ const provenanceScopeAspect = await this.tryGetProvenanceScopeAspect();
452
+ if (!provenanceScopeAspect) {
453
+ return { index: -1, id: "" };
454
+ }
455
+ const version = (await this._isReverseSynchronization())
456
+ ? JSON.parse(provenanceScopeAspect.jsonProperties ?? "{}").reverseSyncVersion
457
+ : provenanceScopeAspect.version;
458
+ if (!version &&
459
+ this._transformerOptions.branchRelationshipDataBehavior ===
460
+ "unsafe-migrate") {
461
+ return { index: -1, id: "" };
462
+ }
463
+ if (version === undefined) {
464
+ core_bentley_1.ITwinError.throwError({
465
+ iTwinErrorId: {
466
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
467
+ key: IModelTransformerError_1.IModelTransformerError.SynchronizationVersionMissing,
468
+ },
469
+ message: `Could not find synchronization version in scope aspect. This may be due to the last successful run of the transformer being done with an older version.
470
+ Consider running the transformer with branchRelationshipDataBehavior set to 'unsafe-migrate'`,
471
+ });
472
+ }
473
+ const [id, index] = version === "" ? ["", -1] : version.split(";");
474
+ if (Number.isNaN(Number(index)))
475
+ throw new Error("Could not parse version data from scope aspect");
476
+ this._cachedSynchronizationVersion = { index: Number(index), id };
477
+ }
478
+ return this._cachedSynchronizationVersion;
479
+ }
480
+ /**
481
+ * Returns the pending changeset indices to skip for the current synchronization direction.
482
+ * Used by changeset initialization to determine which changesets have already been processed.
483
+ */
484
+ async getChangesetsToSkip() {
485
+ if (this._targetScopeProvenanceProps === undefined)
486
+ throw new Error("_targetScopeProvenanceProps should be set by now");
487
+ const props = this._targetScopeProvenanceProps;
488
+ return (await this._isReverseSynchronization())
489
+ ? props.jsonProperties.pendingReverseSyncChangesetIndices
490
+ : props.jsonProperties.pendingSyncChangesetIndices;
491
+ }
492
+ /**
493
+ * Updates the synchronization version on the scope ESA.
494
+ *
495
+ * Called at the end of a transformation, updates the target scope element to record
496
+ * that transformation up through the source's changeset has been performed. Also stores
497
+ * all changesets that occurred during the transformation as "pending synchronization
498
+ * changeset indices" @see TargetScopeProvenanceJsonProps
499
+ *
500
+ * @param initializeReverseSyncVersion When true, saves the reverse sync version as the
501
+ * current changeset of the targetDb. This is typically used for the first transformation
502
+ * between a master and branch iModel. Setting this to true has the effect of making it so
503
+ * any changesets in the branch iModel at the time of the first transformation will be
504
+ * ignored during any future reverse synchronizations from the branch to the master iModel.
505
+ *
506
+ * Note that typically, the reverseSyncVersion is saved as the last changeset merged from
507
+ * the branch into master. Setting initializeReverseSyncVersion to true during a forward
508
+ * transformation could overwrite this correct reverseSyncVersion and should only be done
509
+ * during the first transformation between a master and branch iModel.
510
+ *
511
+ * @param sourceChangeDataState The current state of change data — used to skip updates
512
+ * when there are no changes.
513
+ */
514
+ async updateSynchronizationVersion({ initializeReverseSyncVersion = false, sourceChangeDataState, }) {
515
+ const shouldSkipSyncVersionUpdate = !initializeReverseSyncVersion && sourceChangeDataState !== "has-changes";
516
+ if (shouldSkipSyncVersionUpdate)
517
+ return;
518
+ // If noProvenance is set, there's no scope ESA to update
519
+ if (this._transformerOptions.noProvenance)
520
+ return;
521
+ if (this._targetScopeProvenanceProps === undefined)
522
+ throw new Error("_targetScopeProvenanceProps should be set by now");
523
+ const scopeProps = this._targetScopeProvenanceProps;
524
+ const sourceVersion = `${this._context.sourceDb.changeset.id};${this._context.sourceDb.changeset.index}`;
525
+ const targetVersion = `${this._context.targetDb.changeset.id};${this._context.targetDb.changeset.index}`;
526
+ if (await this._isReverseSynchronization()) {
527
+ const oldVersion = scopeProps.jsonProperties.reverseSyncVersion;
528
+ core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse version from ${oldVersion} to ${sourceVersion}`);
529
+ scopeProps.jsonProperties.reverseSyncVersion = sourceVersion;
530
+ }
531
+ else {
532
+ core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${scopeProps.version} to ${sourceVersion}`);
533
+ scopeProps.version = sourceVersion;
534
+ if (initializeReverseSyncVersion) {
535
+ core_bentley_1.Logger.logInfo(loggerCategory, `updating reverse sync version from ${scopeProps.jsonProperties.reverseSyncVersion} to ${targetVersion}`);
536
+ scopeProps.jsonProperties.reverseSyncVersion = targetVersion;
537
+ }
538
+ }
539
+ const startingChangesetIndices = this._startingChangesetIndices;
540
+ if (!!this._transformerOptions.argsForProcessChanges ||
541
+ (startingChangesetIndices && initializeReverseSyncVersion)) {
542
+ if (this._context.targetDb.changeset.index === undefined ||
543
+ startingChangesetIndices === undefined)
544
+ throw new Error("updateSynchronizationVersion was called without change history");
545
+ const jsonProps = scopeProps.jsonProperties;
546
+ core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingReverseSyncChanges: ${String(jsonProps.pendingReverseSyncChangesetIndices)}`);
547
+ core_bentley_1.Logger.logTrace(loggerCategory, `previous pendingSyncChanges: ${String(jsonProps.pendingSyncChangesetIndices)}`);
548
+ const pendingSyncChangesetIndicesKey = "pendingSyncChangesetIndices";
549
+ const pendingReverseSyncChangesetIndicesKey = "pendingReverseSyncChangesetIndices";
550
+ let syncChangesetsToClearKey;
551
+ let syncChangesetsToUpdateKey;
552
+ if (await this._isReverseSynchronization()) {
553
+ syncChangesetsToClearKey = pendingReverseSyncChangesetIndicesKey;
554
+ syncChangesetsToUpdateKey = pendingSyncChangesetIndicesKey;
555
+ }
556
+ else {
557
+ syncChangesetsToClearKey = pendingSyncChangesetIndicesKey;
558
+ syncChangesetsToUpdateKey = pendingReverseSyncChangesetIndicesKey;
559
+ }
560
+ for (let i = startingChangesetIndices.target + 1; i <= this._context.targetDb.changeset.index + 1; i++)
561
+ jsonProps[syncChangesetsToUpdateKey].push(i);
562
+ jsonProps[syncChangesetsToClearKey] = jsonProps[syncChangesetsToClearKey].filter((csIndex) => {
563
+ return csIndex > startingChangesetIndices.source;
564
+ });
565
+ if (await this._isReverseSynchronization()) {
566
+ if (this._context.sourceDb.changeset.index === undefined)
567
+ throw new Error("changeset didn't exist");
568
+ for (let i = startingChangesetIndices.source + 1; i <= this._context.sourceDb.changeset.index + 1; i++)
569
+ jsonProps.pendingReverseSyncChangesetIndices.push(i);
570
+ }
571
+ core_bentley_1.Logger.logTrace(loggerCategory, `new pendingReverseSyncChanges: ${String(jsonProps.pendingReverseSyncChangesetIndices)}`);
572
+ core_bentley_1.Logger.logTrace(loggerCategory, `new pendingSyncChanges: ${String(jsonProps.pendingSyncChangesetIndices)}`);
573
+ }
574
+ (await this.getProvenanceEditTxn()).updateAspect({
575
+ ...scopeProps,
576
+ jsonProperties: JSON.stringify(scopeProps.jsonProperties),
577
+ });
578
+ this.clearCachedSynchronizationVersion();
579
+ }
580
+ // ── Element/Relationship provenance creation ───────────────────────────
581
+ /** Create ExternalSourceAspectProps for an element in an iModel → iModel transformation. */
582
+ static initElementProvenanceOptions(sourceElementId, targetElementId, args) {
583
+ const elementId = args.isReverseSynchronization
584
+ ? sourceElementId
585
+ : targetElementId;
586
+ const version = args.isReverseSynchronization
587
+ ? args.targetDb.elements.queryLastModifiedTime(targetElementId)
588
+ : args.sourceDb.elements.queryLastModifiedTime(sourceElementId);
589
+ const aspectIdentifier = args.isReverseSynchronization
590
+ ? targetElementId
591
+ : sourceElementId;
592
+ const aspectProps = {
593
+ classFullName: core_backend_1.ExternalSourceAspect.classFullName,
594
+ element: {
595
+ id: elementId,
596
+ relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
597
+ },
598
+ scope: { id: args.targetScopeElementId },
599
+ identifier: aspectIdentifier,
600
+ kind: core_backend_1.ExternalSourceAspect.Kind.Element,
601
+ version,
602
+ };
603
+ return aspectProps;
604
+ }
605
+ /** Create ExternalSourceAspectProps for a relationship in an iModel → iModel transformation. */
606
+ static async initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, args) {
607
+ const provenanceDb = args.isReverseSynchronization
608
+ ? args.sourceDb
609
+ : args.targetDb;
610
+ const aspectIdentifier = args.isReverseSynchronization
611
+ ? targetRelInstanceId
612
+ : sourceRelInstanceId;
613
+ const provenanceRelInstanceId = args.isReverseSynchronization
614
+ ? sourceRelInstanceId
615
+ : targetRelInstanceId;
616
+ // the provenance relationship lives in the provenanceDb, so its known source-endpoint id is
617
+ // the source rel's on reverse synchronization and the target rel's otherwise
618
+ let elementId = args.isReverseSynchronization
619
+ ? args.knownEndpoints?.sourceRelSourceElementId
620
+ : args.knownEndpoints?.targetRelSourceElementId;
621
+ if (undefined === elementId) {
622
+ const sql = "SELECT SourceECInstanceId FROM bis.ElementRefersToElements WHERE ECInstanceId=?";
623
+ const params = new core_common_1.QueryBinder().bindId(1, provenanceRelInstanceId);
624
+ const reader = provenanceDb.createQueryReader(sql, params, {
625
+ usePrimaryConn: true,
626
+ });
627
+ if (!(await reader.step()))
628
+ core_bentley_1.ITwinError.throwError({
629
+ iTwinErrorId: {
630
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
631
+ key: IModelTransformerError_1.IModelTransformerError.RelationshipProvenanceNotFound,
632
+ },
633
+ message: "relationship provenance query returned no rows",
634
+ });
635
+ elementId = reader.current[0];
636
+ }
637
+ const jsonProperties = args.forceOldRelationshipProvenanceMethod
638
+ ? { targetRelInstanceId }
639
+ : { provenanceRelInstanceId };
640
+ const aspectProps = {
641
+ classFullName: core_backend_1.ExternalSourceAspect.classFullName,
642
+ element: {
643
+ id: elementId,
644
+ relClassName: core_backend_1.ElementOwnsExternalSourceAspects.classFullName,
645
+ },
646
+ scope: { id: args.targetScopeElementId },
647
+ identifier: aspectIdentifier,
648
+ kind: core_backend_1.ExternalSourceAspect.Kind.Relationship,
649
+ jsonProperties: JSON.stringify(jsonProperties),
650
+ };
651
+ return aspectProps;
652
+ }
653
+ /** Create an ExternalSourceAspectProps for an element using this manager's context. */
654
+ async initElementProvenance(sourceElementId, targetElementId) {
655
+ return ProvenanceManager.initElementProvenanceOptions(sourceElementId, targetElementId, {
656
+ isReverseSynchronization: await this._isReverseSynchronization(),
657
+ targetScopeElementId: this._targetScopeElementId,
658
+ sourceDb: this._context.sourceDb,
659
+ targetDb: this._context.targetDb,
660
+ });
661
+ }
662
+ /** Create an ExternalSourceAspectProps for a relationship using this manager's context. */
663
+ async initRelationshipProvenance(sourceRelInstanceId, targetRelInstanceId, forceOldRelationshipProvenanceMethod, knownEndpoints) {
664
+ return ProvenanceManager.initRelationshipProvenanceOptions(sourceRelInstanceId, targetRelInstanceId, {
665
+ sourceDb: this._context.sourceDb,
666
+ targetDb: this._context.targetDb,
667
+ isReverseSynchronization: await this._isReverseSynchronization(),
668
+ targetScopeElementId: this._targetScopeElementId,
669
+ forceOldRelationshipProvenanceMethod,
670
+ knownEndpoints,
671
+ });
672
+ }
673
+ // ── Provenance queries ─────────────────────────────────────────────────
674
+ /**
675
+ * Queries the provenanceDb for an ESA whose identifier matches the provided relationship ID.
676
+ * @param entityInProvenanceSourceId ID of the relationship in the provenanceSourceDb
677
+ * @param sourceRelInfo Source relationship class and endpoint info (for legacy fallback)
678
+ */
679
+ async queryProvenanceForRelationship(entityInProvenanceSourceId, sourceRelInfo) {
680
+ const sql = `
681
+ SELECT
682
+ ECInstanceId,
683
+ JSON_EXTRACT(JsonProperties, '$.provenanceRelInstanceId') AS provenanceRelInstId
684
+ FROM Bis.ExternalSourceAspect
685
+ WHERE Kind=?
686
+ AND Scope.Id=?
687
+ AND Identifier=?
688
+ `;
689
+ const params = new core_common_1.QueryBinder();
690
+ params.bindString(1, core_backend_1.ExternalSourceAspect.Kind.Relationship);
691
+ params.bindId(2, this._targetScopeElementId);
692
+ params.bindString(3, entityInProvenanceSourceId);
693
+ const result = (await this.getProvenanceDb()).createQueryReader(sql, params, {
694
+ usePrimaryConn: true,
695
+ });
696
+ if (await result.step()) {
697
+ const aspectId = result.current.id;
698
+ const provenanceRelInstId = result.current.provenanceRelInstId;
699
+ const provenanceRelInstanceId = provenanceRelInstId !== undefined
700
+ ? provenanceRelInstId
701
+ : await this._queryTargetRelId(sourceRelInfo);
702
+ return {
703
+ aspectId,
704
+ relationshipId: provenanceRelInstanceId,
705
+ };
706
+ }
707
+ else
708
+ return undefined;
709
+ }
710
+ /** Instance convenience that calls the static forEachTrackedElement with this manager's context. */
711
+ async forEachTrackedElement(fn) {
712
+ return ProvenanceManager.forEachTrackedElement({
713
+ provenanceSourceDb: await this.getProvenanceSourceDb(),
714
+ provenanceDb: await this.getProvenanceDb(),
715
+ targetScopeElementId: this._targetScopeElementId,
716
+ isReverseSynchronization: await this._isReverseSynchronization(),
717
+ fn,
718
+ skipPropagateChangesToRootElements: this._transformerOptions.skipPropagateChangesToRootElements ?? true,
719
+ });
720
+ }
721
+ }
722
+ exports.ProvenanceManager = ProvenanceManager;
723
+ /** Returns the target scope elements: those that own a Scope ExternalSourceAspect.
724
+ * A transformation creates its target scope's Scope aspect before it exports any aspect, so the result includes the current target scope.
725
+ * @internal
726
+ */
727
+ async function queryTargetScopeElementIds(db) {
728
+ const ids = new Set();
729
+ for await (const row of db.createQueryReader(`SELECT Element.Id id FROM ${core_backend_1.ExternalSourceAspect.classFullName} WHERE Kind = :scopeKind`, new core_common_1.QueryBinder().bindString("scopeKind", core_backend_1.ExternalSourceAspect.Kind.Scope), { usePrimaryConn: true }))
730
+ ids.add(row.id);
731
+ return ids;
732
+ }
733
+ /** Whether an aspect is transformer provenance: a Scope ExternalSourceAspect, or an Element or Relationship ExternalSourceAspect scoped to one of `targetScopeElementIds`.
734
+ * Every target scope counts, not just the current one: an element shared by several target scopes, such as one source imported in parts, carries each scope's provenance.
735
+ * Cleanup never deletes transformer provenance.
736
+ * @internal
737
+ */
738
+ function isTransformerProvenanceAspect(aspect, targetScopeElementIds) {
739
+ if (!(aspect instanceof core_backend_1.ExternalSourceAspect))
740
+ return false;
741
+ if (aspect.kind === core_backend_1.ExternalSourceAspect.Kind.Scope)
742
+ return true;
743
+ return ((aspect.kind === core_backend_1.ExternalSourceAspect.Kind.Element ||
744
+ aspect.kind === core_backend_1.ExternalSourceAspect.Kind.Relationship) &&
745
+ aspect.scope !== undefined &&
746
+ targetScopeElementIds.has(aspect.scope.id));
747
+ }
748
+ /** Returns the `importElementMultiAspects` filter for a transformation that includes source provenance.
749
+ * Source provenance cloned from another transformation looks exactly like another target scope's provenance, so a transformer provenance aspect is only eligible for reuse when an incoming ExternalSourceAspect has the same kind, scope, and identifier.
750
+ * The current scope's own provenance is never eligible: the transformer maintains it separately.
751
+ * @internal
752
+ */
753
+ function sourceProvenanceMatchFilter(incoming, targetScopeElementIds, currentScopeElementId) {
754
+ const incomingKeys = new Set(incoming
755
+ .filter((props) => (0, ElementAspectCleanup_1.isSameClass)(props, core_backend_1.ExternalSourceAspect.classFullName))
756
+ .map((props) => externalSourceAspectKey(props)));
757
+ return (aspect) => {
758
+ if (!isTransformerProvenanceAspect(aspect, targetScopeElementIds))
759
+ return true;
760
+ const scopeElementId = aspect.kind === core_backend_1.ExternalSourceAspect.Kind.Scope
761
+ ? aspect.element.id
762
+ : aspect.scope?.id;
763
+ return (scopeElementId !== currentScopeElementId &&
764
+ incomingKeys.has(externalSourceAspectKey(aspect)));
765
+ };
766
+ }
767
+ /** The natural key of an ExternalSourceAspect on its owner. Kinds and IDs never contain `|`, and the identifier comes last, so distinct keys can't collide. */
768
+ function externalSourceAspectKey(aspect) {
769
+ return `${aspect.kind}|${aspect.scope?.id}|${aspect.identifier}`;
770
+ }
771
+ //# sourceMappingURL=ProvenanceManager.js.map