@itwin/imodel-transformer 0.4.18-fedguidopt.6 → 1.0.1-dev.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 (61) hide show
  1. package/lib/cjs/Algo.d.ts.map +1 -1
  2. package/lib/cjs/Algo.js +3 -4
  3. package/lib/cjs/Algo.js.map +1 -1
  4. package/lib/cjs/BigMap.d.ts.map +1 -1
  5. package/lib/cjs/BigMap.js +1 -1
  6. package/lib/cjs/BigMap.js.map +1 -1
  7. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  8. package/lib/cjs/BranchProvenanceInitializer.js +15 -4
  9. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  10. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -1
  11. package/lib/cjs/DetachedExportElementAspectsStrategy.js +12 -5
  12. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  13. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  14. package/lib/cjs/ECReferenceTypesCache.js +32 -18
  15. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  16. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts +1 -1
  17. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
  18. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js +7 -5
  19. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
  20. package/lib/cjs/ElementCascadingDeleter.d.ts +3 -3
  21. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  22. package/lib/cjs/ElementCascadingDeleter.js +9 -7
  23. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  24. package/lib/cjs/EntityMap.d.ts.map +1 -1
  25. package/lib/cjs/EntityMap.js.map +1 -1
  26. package/lib/cjs/EntityUnifier.d.ts +5 -0
  27. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  28. package/lib/cjs/EntityUnifier.js +22 -35
  29. package/lib/cjs/EntityUnifier.js.map +1 -1
  30. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -1
  31. package/lib/cjs/ExportElementAspectsStrategy.js +5 -4
  32. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  33. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -1
  34. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +9 -5
  35. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  36. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  37. package/lib/cjs/IModelCloneContext.js +23 -12
  38. package/lib/cjs/IModelCloneContext.js.map +1 -1
  39. package/lib/cjs/IModelExporter.d.ts +68 -25
  40. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  41. package/lib/cjs/IModelExporter.js +241 -123
  42. package/lib/cjs/IModelExporter.js.map +1 -1
  43. package/lib/cjs/IModelImporter.d.ts +13 -22
  44. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  45. package/lib/cjs/IModelImporter.js +80 -62
  46. package/lib/cjs/IModelImporter.js.map +1 -1
  47. package/lib/cjs/IModelTransformer.d.ts +126 -44
  48. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  49. package/lib/cjs/IModelTransformer.js +706 -535
  50. package/lib/cjs/IModelTransformer.js.map +1 -1
  51. package/lib/cjs/PendingReferenceMap.d.ts.map +1 -1
  52. package/lib/cjs/PendingReferenceMap.js +12 -6
  53. package/lib/cjs/PendingReferenceMap.js.map +1 -1
  54. package/lib/cjs/TransformerLoggerCategory.d.ts +2 -2
  55. package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -1
  56. package/lib/cjs/TransformerLoggerCategory.js +5 -5
  57. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  58. package/lib/cjs/transformer.d.ts.map +1 -1
  59. package/lib/cjs/transformer.js +14 -10
  60. package/lib/cjs/transformer.js.map +1 -1
  61. package/package.json +20 -17
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
- * See LICENSE.md in the project root for license terms and full copyright notice.
5
- *--------------------------------------------------------------------------------------------*/
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
6
  /** @packageDocumentation
7
7
  * @module iModels
8
8
  */
@@ -26,7 +26,9 @@ class IModelExportHandler {
26
26
  /** If `true` is returned, then the CodeSpec will be exported.
27
27
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
28
28
  */
29
- shouldExportCodeSpec(_codeSpec) { return true; }
29
+ shouldExportCodeSpec(_codeSpec) {
30
+ return true;
31
+ }
30
32
  /** Called when a CodeSpec should be exported.
31
33
  * @param codeSpec The CodeSpec to export
32
34
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -50,7 +52,9 @@ class IModelExportHandler {
50
52
  /** If `true` is returned, then the element will be exported.
51
53
  * @note This method can optionally be overridden to exclude an individual Element (and its children and ElementAspects) from the export. The base implementation always returns `true`.
52
54
  */
53
- shouldExportElement(_element) { return true; }
55
+ shouldExportElement(_element) {
56
+ return true;
57
+ }
54
58
  /** Called when element is skipped instead of exported. */
55
59
  onSkipElement(_elementId) { }
56
60
  /** Called when an element should be exported.
@@ -71,7 +75,9 @@ class IModelExportHandler {
71
75
  /** If `true` is returned, then the ElementAspect will be exported.
72
76
  * @note This method can optionally be overridden to exclude an individual ElementAspect from the export. The base implementation always returns `true`.
73
77
  */
74
- shouldExportElementAspect(_aspect) { return true; }
78
+ shouldExportElementAspect(_aspect) {
79
+ return true;
80
+ }
75
81
  /** Called when an ElementUniqueAspect should be exported.
76
82
  * @param aspect The ElementUniqueAspect to export
77
83
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -85,7 +91,9 @@ class IModelExportHandler {
85
91
  /** If `true` is returned, then the relationship will be exported.
86
92
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
87
93
  */
88
- shouldExportRelationship(_relationship) { return true; }
94
+ shouldExportRelationship(_relationship) {
95
+ return true;
96
+ }
89
97
  /** Called when a Relationship should be exported.
90
98
  * @param relationship The Relationship to export
91
99
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -97,7 +105,9 @@ class IModelExportHandler {
97
105
  /** If `true` is returned, then the schema will be exported.
98
106
  * @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
99
107
  */
100
- shouldExportSchema(_schemaKey) { return true; }
108
+ shouldExportSchema(_schemaKey) {
109
+ return true;
110
+ }
101
111
  /** Called when a schema should be exported.
102
112
  * @param schema The schema to export
103
113
  * @note This should be overridden to actually do the export.
@@ -121,7 +131,7 @@ class IModelExporter {
121
131
  /**
122
132
  * Retrieve the cached entity change information.
123
133
  * @note This will only be initialized after [IModelExporter.exportChanges] is invoked.
124
- */
134
+ */
125
135
  get sourceDbChanges() {
126
136
  return this._sourceDbChanges;
127
137
  }
@@ -194,11 +204,14 @@ class IModelExporter {
194
204
  * you pass to [[IModelExporter.exportChanges]]
195
205
  */
196
206
  async initialize(options) {
197
- const hasChangeData = options.startChangeset || options.changesetRanges || options.changedInstanceIds;
198
- if (this._sourceDbChanges || !this.sourceDb.isBriefcaseDb() || !hasChangeData)
207
+ if (!this.sourceDb.isBriefcaseDb() || this._sourceDbChanges)
208
+ return;
209
+ this._sourceDbChanges = await ChangedInstanceIds.initialize({
210
+ iModel: this.sourceDb,
211
+ ...options,
212
+ });
213
+ if (this._sourceDbChanges === undefined)
199
214
  return;
200
- this._sourceDbChanges = options.changedInstanceIds
201
- ?? await ChangedInstanceIds.initialize({ iModel: this.sourceDb, ...options });
202
215
  this._exportElementAspectsStrategy.setAspectChanges(this._sourceDbChanges.aspect);
203
216
  }
204
217
  /** Register the handler that will be called by IModelExporter. */
@@ -257,8 +270,13 @@ class IModelExporter {
257
270
  nodeAssert(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
258
271
  await this.exportCodeSpecs();
259
272
  await this.exportFonts();
260
- await this.exportModelContents(core_common_1.IModel.repositoryModelId);
261
- await this.exportSubModels(core_common_1.IModel.repositoryModelId);
273
+ if (initOpts.skipPropagateChangesToRootElements) {
274
+ await this.exportModelContents(core_common_1.IModel.repositoryModelId);
275
+ await this.exportSubModels(core_common_1.IModel.repositoryModelId);
276
+ }
277
+ else {
278
+ await this.exportModel(core_common_1.IModel.repositoryModelId);
279
+ }
262
280
  await this.exportAllAspects();
263
281
  await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
264
282
  // handle deletes
@@ -278,14 +296,16 @@ class IModelExporter {
278
296
  this.handler.onDeleteElement(elementId);
279
297
  }
280
298
  catch (err) {
281
- const isMissingErr = err instanceof core_common_1.IModelError && err.errorNumber === core_bentley_1.IModelStatus.NotFound;
299
+ const isMissingErr = err instanceof core_common_1.IModelError &&
300
+ err.errorNumber === core_bentley_1.IModelStatus.NotFound;
282
301
  if (!isMissingErr)
283
302
  throw err;
284
303
  }
285
304
  }
286
305
  }
287
306
  if (this.visitRelationships) {
288
- for (const relInstanceId of this._sourceDbChanges.relationship.deleteIds) {
307
+ for (const relInstanceId of this._sourceDbChanges.relationship
308
+ .deleteIds) {
289
309
  this.handler.onDeleteRelationship(relInstanceId);
290
310
  }
291
311
  }
@@ -304,7 +324,9 @@ class IModelExporter {
304
324
  const sql = `
305
325
  SELECT s.Name, s.VersionMajor, s.VersionWrite, s.VersionMinor
306
326
  FROM ECDbMeta.ECSchemaDef s
307
- ${this.wantSystemSchemas ? "" : `
327
+ ${this.wantSystemSchemas
328
+ ? ""
329
+ : `
308
330
  WHERE ECInstanceId >= (SELECT ECInstanceId FROM ECDbMeta.ECSchemaDef WHERE Name='BisCore')
309
331
  `}
310
332
  ORDER BY ECInstanceId
@@ -340,8 +362,8 @@ class IModelExporter {
340
362
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
341
363
  */
342
364
  async exportCodeSpecs() {
343
- core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpecs()`);
344
- const sql = `SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId`;
365
+ core_bentley_1.Logger.logTrace(loggerCategory, "exportCodeSpecs()");
366
+ const sql = "SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId";
345
367
  await this.sourceDb.withPreparedStatement(sql, async (statement) => {
346
368
  while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
347
369
  const codeSpecName = statement.getValue(0).getString();
@@ -355,7 +377,8 @@ class IModelExporter {
355
377
  async exportCodeSpecByName(codeSpecName) {
356
378
  const codeSpec = this.sourceDb.codeSpecs.getByName(codeSpecName);
357
379
  let isUpdate;
358
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
380
+ if (undefined !== this._sourceDbChanges) {
381
+ // is changeset information available?
359
382
  if (this._sourceDbChanges.codeSpec.insertIds.has(codeSpec.id)) {
360
383
  isUpdate = false;
361
384
  }
@@ -389,7 +412,7 @@ class IModelExporter {
389
412
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
390
413
  */
391
414
  async exportFonts() {
392
- core_bentley_1.Logger.logTrace(loggerCategory, `exportFonts()`);
415
+ core_bentley_1.Logger.logTrace(loggerCategory, "exportFonts()");
393
416
  for (const font of this.sourceDb.fontMap.fonts.values()) {
394
417
  await this.exportFontByNumber(font.id);
395
418
  }
@@ -408,19 +431,12 @@ class IModelExporter {
408
431
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
409
432
  */
410
433
  async exportFontByNumber(fontNumber) {
411
- let isUpdate;
412
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
413
- const fontId = core_bentley_1.Id64.fromUint32Pair(fontNumber, 0); // changeset information uses Id64String, not number
414
- if (this._sourceDbChanges.font.insertIds.has(fontId)) {
415
- isUpdate = false;
416
- }
417
- else if (this._sourceDbChanges.font.updateIds.has(fontId)) {
418
- isUpdate = true;
419
- }
420
- else {
421
- return; // not in changeset, don't export
422
- }
423
- }
434
+ /** sourceDbChanges now works by using TS ChangesetECAdaptor which doesn't pick up changes to fonts since fonts is not an ec table.
435
+ * So lets always export fonts for the time being by always setting isUpdate = true.
436
+ * It is very rare and even problematic for the font table to reach a large size, so it is not a bottleneck in transforming changes.
437
+ * See https://github.com/iTwin/imodel-transformer/pull/135 for removed code.
438
+ */
439
+ const isUpdate = true;
424
440
  core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
425
441
  const font = this.sourceDb.fontMap.getFont(fontNumber);
426
442
  if (undefined !== font) {
@@ -436,7 +452,11 @@ class IModelExporter {
436
452
  if (model.isTemplate && !this.wantTemplateModels) {
437
453
  return;
438
454
  }
439
- const modeledElement = this.sourceDb.elements.getElement({ id: modeledElementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
455
+ const modeledElement = this.sourceDb.elements.getElement({
456
+ id: modeledElementId,
457
+ wantGeometry: this.wantGeometry,
458
+ wantBRepData: this.wantGeometry,
459
+ });
440
460
  core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
441
461
  if (this.shouldExportElement(modeledElement)) {
442
462
  await this.exportModelContainer(model);
@@ -449,7 +469,8 @@ class IModelExporter {
449
469
  /** Export the model (the container only) from the source iModel. */
450
470
  async exportModelContainer(model) {
451
471
  let isUpdate;
452
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
472
+ if (undefined !== this._sourceDbChanges) {
473
+ // is changeset information available?
453
474
  if (this._sourceDbChanges.model.insertIds.has(model.id)) {
454
475
  isUpdate = false;
455
476
  }
@@ -479,8 +500,10 @@ class IModelExporter {
479
500
  core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportModelContents(${modelId})`);
480
501
  return;
481
502
  }
482
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
483
- if (!this._sourceDbChanges.model.insertIds.has(modelId) && !this._sourceDbChanges.model.updateIds.has(modelId)) {
503
+ if (undefined !== this._sourceDbChanges) {
504
+ // is changeset information available?
505
+ if (!this._sourceDbChanges.model.insertIds.has(modelId) &&
506
+ !this._sourceDbChanges.model.updateIds.has(modelId)) {
484
507
  return; // this optimization assumes that the Model changes (LastMod) any time an Element in the Model changes
485
508
  }
486
509
  }
@@ -547,7 +570,8 @@ class IModelExporter {
547
570
  return false;
548
571
  }
549
572
  }
550
- if (!this.wantTemplateModels && (element instanceof core_backend_1.RecipeDefinitionElement)) {
573
+ if (!this.wantTemplateModels &&
574
+ element instanceof core_backend_1.RecipeDefinitionElement) {
551
575
  core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${element.id} because wantTemplate=false`);
552
576
  return false;
553
577
  }
@@ -569,10 +593,16 @@ class IModelExporter {
569
593
  return;
570
594
  }
571
595
  // are we processing changes?
572
- const isUpdate = this._sourceDbChanges?.element.insertIds.has(elementId) ? false
573
- : this._sourceDbChanges?.element.updateIds.has(elementId) ? true
596
+ const isUpdate = this._sourceDbChanges?.element.insertIds.has(elementId)
597
+ ? false
598
+ : this._sourceDbChanges?.element.updateIds.has(elementId)
599
+ ? true
574
600
  : undefined;
575
- const element = this.sourceDb.elements.getElement({ id: elementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
601
+ const element = this.sourceDb.elements.getElement({
602
+ id: elementId,
603
+ wantGeometry: this.wantGeometry,
604
+ wantBRepData: this.wantGeometry,
605
+ });
576
606
  core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
577
607
  // the order and `await`ing of calls beyond here is depended upon by the IModelTransformer for a current bug workaround
578
608
  if (this.shouldExportElement(element)) {
@@ -612,7 +642,7 @@ class IModelExporter {
612
642
  */
613
643
  async exportRelationships(baseRelClassFullName) {
614
644
  if (!this.visitRelationships) {
615
- core_bentley_1.Logger.logTrace(loggerCategory, `visitRelationships=false, skipping exportRelationships()`);
645
+ core_bentley_1.Logger.logTrace(loggerCategory, "visitRelationships=false, skipping exportRelationships()");
616
646
  return;
617
647
  }
618
648
  core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
@@ -623,7 +653,9 @@ class IModelExporter {
623
653
  await this.sourceDb.withPreparedStatement(sql, async (statement) => {
624
654
  while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
625
655
  const relationshipId = statement.getValue(0).getId();
626
- const relationshipClass = statement.getValue(1).getClassNameForClassId();
656
+ const relationshipClass = statement
657
+ .getValue(1)
658
+ .getClassNameForClassId();
627
659
  await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
628
660
  await this._yieldManager.allowYield();
629
661
  }
@@ -636,7 +668,8 @@ class IModelExporter {
636
668
  return;
637
669
  }
638
670
  let isUpdate;
639
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
671
+ if (undefined !== this._sourceDbChanges) {
672
+ // is changeset information available?
640
673
  if (this._sourceDbChanges.relationship.insertIds.has(relInstanceId)) {
641
674
  isUpdate = false;
642
675
  }
@@ -665,7 +698,7 @@ class IModelExporter {
665
698
  /** Tracks incremental progress */
666
699
  async trackProgress() {
667
700
  this._progressCounter++;
668
- if (0 === (this._progressCounter % this.progressInterval)) {
701
+ if (0 === this._progressCounter % this.progressInterval) {
669
702
  return this.handler.onProgress();
670
703
  }
671
704
  }
@@ -696,9 +729,9 @@ class IModelExporter {
696
729
  this.visitElements = state.visitElements;
697
730
  this.visitRelationships = state.visitRelationships;
698
731
  this._excludedCodeSpecNames = new Set(state.excludedCodeSpecNames);
699
- this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds),
700
- this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds),
701
- this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c)));
732
+ (this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds)),
733
+ (this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds)),
734
+ (this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c))));
702
735
  this._exportElementAspectsStrategy.loadExcludedElementAspectClasses(state.excludedElementAspectClassFullNames);
703
736
  this._excludedRelationshipClasses = new Set(state.excludedRelationshipClassNames.map((c) => this.sourceDb.getJsClass(c)));
704
737
  this.loadAdditionalStateJson(state.additionalState);
@@ -721,39 +754,19 @@ class IModelExporter {
721
754
  excludedElementIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementIds),
722
755
  excludedElementCategoryIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementCategoryIds),
723
756
  excludedElementClassNames: Array.from(this._excludedElementClasses, (cls) => cls.classFullName),
724
- excludedElementAspectClassFullNames: [...this._exportElementAspectsStrategy.excludedElementAspectClassFullNames],
757
+ excludedElementAspectClassFullNames: [
758
+ ...this._exportElementAspectsStrategy
759
+ .excludedElementAspectClassFullNames,
760
+ ],
725
761
  excludedRelationshipClassNames: Array.from(this._excludedRelationshipClasses, (cls) => cls.classFullName),
726
762
  additionalState: this.getAdditionalStateJson(),
727
763
  };
728
764
  }
729
765
  }
730
766
  exports.IModelExporter = IModelExporter;
731
- /**
732
- * Asserts that the passed in options have exactly one of:
733
- * startChangeset xor changesetRanges xor changedInstanceIds
734
- * defined
735
- */
736
- function assertHasChangeDataOptions(opts) {
737
- const xor = (...args) => {
738
- let result = false;
739
- for (const a of args) {
740
- if (!result && a)
741
- result = true;
742
- else if (result && a)
743
- return false;
744
- }
745
- return result;
746
- };
747
- nodeAssert(xor(opts.startChangeset, opts.changesetRanges, opts.changedInstanceIds), "exactly one of startChangeset, XOR changesetRanges XOR opts.changedInstanceIds may be defined but "
748
- + `received ${JSON.stringify({
749
- startChangeset: !!opts.startChangeset,
750
- changesetRanges: !!opts.changesetRanges,
751
- ChangedInstanceIds: !!opts.changedInstanceIds,
752
- })}`);
753
- }
754
767
  /** Class for holding change information.
755
768
  * @beta
756
- */
769
+ */
757
770
  class ChangedInstanceOps {
758
771
  constructor() {
759
772
  this.insertIds = new Set();
@@ -763,11 +776,11 @@ class ChangedInstanceOps {
763
776
  /** Initializes the object from IModelJsNative.ChangedInstanceOpsProps. */
764
777
  addFromJson(val) {
765
778
  if (undefined !== val) {
766
- if ((undefined !== val.insert) && (Array.isArray(val.insert)))
779
+ if (undefined !== val.insert && Array.isArray(val.insert))
767
780
  val.insert.forEach((id) => this.insertIds.add(id));
768
- if ((undefined !== val.update) && (Array.isArray(val.update)))
781
+ if (undefined !== val.update && Array.isArray(val.update))
769
782
  val.update.forEach((id) => this.updateIds.add(id));
770
- if ((undefined !== val.delete) && (Array.isArray(val.delete)))
783
+ if (undefined !== val.delete && Array.isArray(val.delete))
771
784
  val.delete.forEach((id) => this.deleteIds.add(id));
772
785
  }
773
786
  }
@@ -778,69 +791,174 @@ exports.ChangedInstanceOps = ChangedInstanceOps;
778
791
  * @beta
779
792
  */
780
793
  class ChangedInstanceIds {
781
- constructor() {
794
+ constructor(db) {
782
795
  this.codeSpec = new ChangedInstanceOps();
783
796
  this.model = new ChangedInstanceOps();
784
797
  this.element = new ChangedInstanceOps();
785
798
  this.aspect = new ChangedInstanceOps();
786
799
  this.relationship = new ChangedInstanceOps();
787
800
  this.font = new ChangedInstanceOps();
801
+ this._db = db;
802
+ }
803
+ async setupECClassIds() {
804
+ this._codeSpecSubclassIds = new Set();
805
+ this._modelSubclassIds = new Set();
806
+ this._elementSubclassIds = new Set();
807
+ this._aspectSubclassIds = new Set();
808
+ this._relationshipSubclassIds = new Set();
809
+ const addECClassIdsToSet = async (setToModify, baseClass) => {
810
+ for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`)) {
811
+ setToModify.add(row.ECInstanceId);
812
+ }
813
+ };
814
+ const promises = [
815
+ addECClassIdsToSet(this._codeSpecSubclassIds, "BisCore.CodeSpec"),
816
+ addECClassIdsToSet(this._modelSubclassIds, "BisCore.Model"),
817
+ addECClassIdsToSet(this._elementSubclassIds, "BisCore.Element"),
818
+ addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementUniqueAspect"),
819
+ addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementMultiAspect"),
820
+ addECClassIdsToSet(this._relationshipSubclassIds, "BisCore.ElementRefersToElements"),
821
+ ];
822
+ await Promise.all(promises);
823
+ }
824
+ get _ecClassIdsInitialized() {
825
+ return (this._codeSpecSubclassIds &&
826
+ this._modelSubclassIds &&
827
+ this._elementSubclassIds &&
828
+ this._aspectSubclassIds &&
829
+ this._relationshipSubclassIds);
830
+ }
831
+ isRelationship(ecClassId) {
832
+ return this._relationshipSubclassIds?.has(ecClassId);
833
+ }
834
+ isCodeSpec(ecClassId) {
835
+ return this._codeSpecSubclassIds?.has(ecClassId);
836
+ }
837
+ isAspect(ecClassId) {
838
+ return this._aspectSubclassIds?.has(ecClassId);
839
+ }
840
+ isModel(ecClassId) {
841
+ return this._modelSubclassIds?.has(ecClassId);
842
+ }
843
+ isElement(ecClassId) {
844
+ return this._elementSubclassIds?.has(ecClassId);
845
+ }
846
+ /**
847
+ * Adds the provided [[ChangedECInstance]] to the appropriate set of changes by class type (codeSpec, model, element, aspect, or relationship) maintained by this instance of ChangedInstanceIds.
848
+ * If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
849
+ * from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
850
+ * @param change ChangedECInstance which has the ECInstanceId, changeType (insert, update, delete) and ECClassId of the changed entity
851
+ */
852
+ async addChange(change) {
853
+ if (!this._ecClassIdsInitialized)
854
+ await this.setupECClassIds();
855
+ const ecClassId = change.ECClassId ?? change.$meta?.fallbackClassId;
856
+ if (ecClassId === undefined)
857
+ throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
858
+ const changeType = change.$meta?.op;
859
+ if (changeType === undefined)
860
+ throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
861
+ if (this.isRelationship(ecClassId))
862
+ this.handleChange(this.relationship, changeType, change.ECInstanceId);
863
+ else if (this.isCodeSpec(ecClassId))
864
+ this.handleChange(this.codeSpec, changeType, change.ECInstanceId);
865
+ else if (this.isAspect(ecClassId))
866
+ this.handleChange(this.aspect, changeType, change.ECInstanceId);
867
+ else if (this.isModel(ecClassId))
868
+ this.handleChange(this.model, changeType, change.ECInstanceId);
869
+ else if (this.isElement(ecClassId))
870
+ this.handleChange(this.element, changeType, change.ECInstanceId);
871
+ }
872
+ handleChange(changedInstanceOps, changeType, id) {
873
+ // if changeType is a delete and we already have the id in the inserts then we can remove the id from the inserts.
874
+ // if changeType is a delete and we already have the id in the updates then we can remove the id from the updates AND add it to the deletes.
875
+ // if changeType is an insert and we already have the id in the deletes then we can remove the id from the deletes AND add it to the inserts.
876
+ if (changeType === "Inserted") {
877
+ changedInstanceOps.insertIds.add(id);
878
+ changedInstanceOps.deleteIds.delete(id);
879
+ }
880
+ else if (changeType === "Updated") {
881
+ if (!changedInstanceOps.insertIds.has(id))
882
+ changedInstanceOps.updateIds.add(id);
883
+ }
884
+ else if (changeType === "Deleted") {
885
+ // If we've inserted the entity at some point already and now we're seeing a delete. We can simply remove the entity from our inserted ids without adding it to deletedIds.
886
+ if (changedInstanceOps.insertIds.has(id))
887
+ changedInstanceOps.insertIds.delete(id);
888
+ else {
889
+ changedInstanceOps.updateIds.delete(id);
890
+ changedInstanceOps.deleteIds.add(id);
891
+ }
892
+ }
788
893
  }
789
894
  /**
790
895
  * Initializes a new ChangedInstanceIds object with information taken from a range of changesets.
791
- * @deprecated in 0.1.x. Pass a [[ChangedInstanceIdsInitOptions]] object instead of a changeset id
792
- * @param accessToken Access token.
793
- * @param iModel IModel briefcase whose changesets will be queried.
794
- * @param firstChangesetId Changeset id.
795
- * @note Modified element information will be taken from a range of changesets. First changeset in a range will be the 'firstChangesetId', the last will be whichever changeset the 'iModel' briefcase is currently opened on.
796
- */
797
- static async initialize(accessTokenOrOpts, iModel, startChangesetId) {
798
- const opts = typeof accessTokenOrOpts === "object"
799
- ? accessTokenOrOpts
800
- : {
801
- accessToken: accessTokenOrOpts,
802
- iModel: iModel,
803
- startChangeset: { id: startChangesetId },
804
- };
805
- assertHasChangeDataOptions(opts);
896
+ */
897
+ static async initialize(opts) {
898
+ if ("changedInstanceIds" in opts)
899
+ return opts.changedInstanceIds;
806
900
  const iModelId = opts.iModel.iModelId;
807
901
  const accessToken = opts.accessToken;
808
- const changesetRanges = opts.changesetRanges
809
- ?? [[
810
- // we know startChangeset is defined because of the assert above
811
- opts.startChangeset.index
812
- ?? (await core_backend_1.IModelHost.hubAccess.queryChangeset({
902
+ const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
903
+ const changesetRanges = startChangeset !== undefined
904
+ ? [
905
+ [
906
+ startChangeset.index ??
907
+ (await core_backend_1.IModelHost.hubAccess.queryChangeset({
813
908
  iModelId,
814
- changeset: { id: opts.startChangeset.id ?? opts.iModel.changeset.id },
909
+ changeset: {
910
+ id: startChangeset.id ?? opts.iModel.changeset.id,
911
+ },
815
912
  accessToken,
816
913
  })).index,
817
- opts.iModel.changeset.index
818
- ?? (await core_backend_1.IModelHost.hubAccess.queryChangeset({
914
+ opts.iModel.changeset.index ??
915
+ (await core_backend_1.IModelHost.hubAccess.queryChangeset({
819
916
  iModelId,
820
917
  changeset: { id: opts.iModel.changeset.id },
821
918
  accessToken,
822
919
  })).index,
823
- ]];
824
- core_bentley_1.Logger.logTrace(loggerCategory, `ChangedInstanceIds.initialize ranges: ${changesetRanges.join("|")}`);
825
- const changesets = (await Promise.all(changesetRanges.map(async ([first, end]) => core_backend_1.IModelHost.hubAccess.downloadChangesets({
826
- accessToken,
827
- iModelId, range: { first, end },
828
- targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId),
829
- })))).flat();
830
- const changedInstanceIds = new ChangedInstanceIds();
831
- const changesetFiles = changesets.map((c) => c.pathname);
832
- const statusOrResult = opts.iModel.nativeDb.extractChangedInstanceIdsFromChangeSets(changesetFiles);
833
- if (statusOrResult.error) {
834
- throw new core_common_1.IModelError(statusOrResult.error.status, "Error processing changeset");
835
- }
836
- const result = statusOrResult.result;
837
- (0, core_bentley_1.assert)(result !== undefined);
838
- changedInstanceIds.codeSpec.addFromJson(result.codeSpec);
839
- changedInstanceIds.model.addFromJson(result.model);
840
- changedInstanceIds.element.addFromJson(result.element);
841
- changedInstanceIds.aspect.addFromJson(result.aspect);
842
- changedInstanceIds.relationship.addFromJson(result.relationship);
843
- changedInstanceIds.font.addFromJson(result.font);
920
+ ],
921
+ ]
922
+ : "changesetRanges" in opts
923
+ ? opts.changesetRanges
924
+ : undefined;
925
+ const csFileProps = changesetRanges !== undefined
926
+ ? (await Promise.all(changesetRanges.map(async ([first, end]) => core_backend_1.IModelHost.hubAccess.downloadChangesets({
927
+ accessToken,
928
+ iModelId,
929
+ range: { first, end },
930
+ targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId),
931
+ })))).flat()
932
+ : "csFileProps" in opts
933
+ ? opts.csFileProps
934
+ : undefined;
935
+ if (csFileProps === undefined)
936
+ return undefined;
937
+ const changedInstanceIds = new ChangedInstanceIds(opts.iModel);
938
+ const relationshipECClassIdsToSkip = new Set();
939
+ for await (const row of opts.iModel.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
940
+ relationshipECClassIdsToSkip.add(row.ECInstanceId);
941
+ }
942
+ for (const csFile of csFileProps) {
943
+ const csReader = core_backend_1.SqliteChangesetReader.openFile({
944
+ fileName: csFile.pathname,
945
+ db: opts.iModel,
946
+ disableSchemaCheck: true,
947
+ });
948
+ const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
949
+ const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier();
950
+ while (csAdaptor.step()) {
951
+ ecChangeUnifier.appendFrom(csAdaptor);
952
+ }
953
+ const changes = [...ecChangeUnifier.instances];
954
+ for (const change of changes) {
955
+ if (change.ECClassId !== undefined &&
956
+ relationshipECClassIdsToSkip.has(change.ECClassId))
957
+ continue;
958
+ await changedInstanceIds.addChange(change);
959
+ }
960
+ csReader.close();
961
+ }
844
962
  return changedInstanceIds;
845
963
  }
846
964
  }