@itwin/tree-widget-react 3.16.1 → 3.17.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 (142) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/lib/cjs/tree-widget-react/components/SelectableTree.js.map +1 -1
  3. package/lib/cjs/tree-widget-react/components/TreeSelector.js.map +1 -1
  4. package/lib/cjs/tree-widget-react/components/TreeWidgetUiItemsProvider.js +1 -1
  5. package/lib/cjs/tree-widget-react/components/TreeWidgetUiItemsProvider.js.map +1 -1
  6. package/lib/cjs/tree-widget-react/components/tree-header/TreeHeader.js.map +1 -1
  7. package/lib/cjs/tree-widget-react/components/tree-header/TreeWithHeader.js.map +1 -1
  8. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.d.ts +2 -2
  9. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.js +2 -1
  10. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTree.js.map +1 -1
  11. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  12. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.d.ts +1 -1
  13. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.js.map +1 -1
  14. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.d.ts +13 -0
  15. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +92 -74
  16. package/lib/cjs/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  17. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.d.ts +3 -1
  18. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js +21 -8
  19. package/lib/cjs/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js.map +1 -1
  20. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +21 -9
  21. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +300 -251
  22. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  23. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.d.ts +2 -0
  24. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +66 -107
  25. package/lib/cjs/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  26. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +22 -12
  27. package/lib/cjs/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  28. package/lib/cjs/tree-widget-react/components/trees/common/UseActiveViewport.js.map +1 -1
  29. package/lib/cjs/tree-widget-react/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  30. package/lib/cjs/tree-widget-react/components/trees/common/components/ProgressOverlay.js.map +1 -1
  31. package/lib/cjs/tree-widget-react/components/trees/common/components/Tree.d.ts +2 -2
  32. package/lib/cjs/tree-widget-react/components/trees/common/components/Tree.js.map +1 -1
  33. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.d.ts +1 -1
  34. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  35. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.d.ts +1 -1
  36. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.js +2 -2
  37. package/lib/cjs/tree-widget-react/components/trees/common/components/TreeRenderer.js.map +1 -1
  38. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTree.d.ts +3 -3
  39. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTree.js.map +1 -1
  40. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.d.ts +1 -1
  41. package/lib/cjs/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.js.map +1 -1
  42. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  43. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js +83 -0
  44. package/lib/cjs/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  45. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTree.d.ts +1 -1
  46. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTree.js.map +1 -1
  47. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  48. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  49. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  50. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +86 -28
  51. package/lib/cjs/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  52. package/lib/cjs/tree-widget-react/components/trees/models-tree/UseModelsTree.js +2 -1
  53. package/lib/cjs/tree-widget-react/components/trees/models-tree/UseModelsTree.js.map +1 -1
  54. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -6
  55. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js +5 -17
  56. package/lib/cjs/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  57. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +5 -4
  58. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  59. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  60. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  61. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  62. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +28 -12
  63. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +382 -278
  64. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  65. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.d.ts +1 -1
  66. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.js.map +1 -1
  67. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +272 -139
  68. package/lib/cjs/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  69. package/lib/cjs/tree-widget-react-internal.d.ts +2 -1
  70. package/lib/cjs/tree-widget-react-internal.js +4 -1
  71. package/lib/cjs/tree-widget-react-internal.js.map +1 -1
  72. package/lib/esm/tree-widget-react/components/SelectableTree.js.map +1 -1
  73. package/lib/esm/tree-widget-react/components/TreeSelector.js.map +1 -1
  74. package/lib/esm/tree-widget-react/components/TreeWidgetUiItemsProvider.js +1 -1
  75. package/lib/esm/tree-widget-react/components/TreeWidgetUiItemsProvider.js.map +1 -1
  76. package/lib/esm/tree-widget-react/components/tree-header/TreeHeader.js.map +1 -1
  77. package/lib/esm/tree-widget-react/components/tree-header/TreeWithHeader.js.map +1 -1
  78. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.d.ts +2 -2
  79. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.js +2 -1
  80. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTree.js.map +1 -1
  81. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeButtons.js.map +1 -1
  82. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.d.ts +1 -1
  83. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeComponent.js.map +1 -1
  84. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.d.ts +13 -0
  85. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js +92 -74
  86. package/lib/esm/tree-widget-react/components/trees/categories-tree/CategoriesTreeDefinition.js.map +1 -1
  87. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.d.ts +3 -1
  88. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js +22 -9
  89. package/lib/esm/tree-widget-react/components/trees/categories-tree/UseCategoriesTree.js.map +1 -1
  90. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.d.ts +21 -9
  91. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js +301 -252
  92. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesTreeIdsCache.js.map +1 -1
  93. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.d.ts +2 -0
  94. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js +67 -108
  95. package/lib/esm/tree-widget-react/components/trees/categories-tree/internal/CategoriesVisibilityHandler.js.map +1 -1
  96. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js +23 -13
  97. package/lib/esm/tree-widget-react/components/trees/common/CategoriesVisibilityUtils.js.map +1 -1
  98. package/lib/esm/tree-widget-react/components/trees/common/UseActiveViewport.js.map +1 -1
  99. package/lib/esm/tree-widget-react/components/trees/common/UseHierarchyVisibility.js.map +1 -1
  100. package/lib/esm/tree-widget-react/components/trees/common/components/ProgressOverlay.js.map +1 -1
  101. package/lib/esm/tree-widget-react/components/trees/common/components/Tree.d.ts +2 -2
  102. package/lib/esm/tree-widget-react/components/trees/common/components/Tree.js.map +1 -1
  103. package/lib/esm/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.d.ts +1 -1
  104. package/lib/esm/tree-widget-react/components/trees/common/components/TreeNodeCheckbox.js.map +1 -1
  105. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.d.ts +1 -1
  106. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.js +2 -2
  107. package/lib/esm/tree-widget-react/components/trees/common/components/TreeRenderer.js.map +1 -1
  108. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTree.d.ts +3 -3
  109. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTree.js.map +1 -1
  110. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.d.ts +1 -1
  111. package/lib/esm/tree-widget-react/components/trees/common/components/VisibilityTreeRenderer.js.map +1 -1
  112. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.d.ts +25 -0
  113. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js +74 -0
  114. package/lib/esm/tree-widget-react/components/trees/common/internal/Utils.js.map +1 -0
  115. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTree.d.ts +1 -1
  116. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTree.js.map +1 -1
  117. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeButtons.js.map +1 -1
  118. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeComponent.js.map +1 -1
  119. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.d.ts +1 -0
  120. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js +83 -25
  121. package/lib/esm/tree-widget-react/components/trees/models-tree/ModelsTreeDefinition.js.map +1 -1
  122. package/lib/esm/tree-widget-react/components/trees/models-tree/UseModelsTree.js +2 -1
  123. package/lib/esm/tree-widget-react/components/trees/models-tree/UseModelsTree.js.map +1 -1
  124. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.d.ts +5 -6
  125. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js +4 -16
  126. package/lib/esm/tree-widget-react/components/trees/models-tree/Utils.js.map +1 -1
  127. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js +4 -3
  128. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/AlwaysAndNeverDrawnElementInfo.js.map +1 -1
  129. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.d.ts +7 -1
  130. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js +52 -0
  131. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/FilteredTree.js.map +1 -1
  132. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.d.ts +28 -12
  133. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js +382 -278
  134. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeIdsCache.js.map +1 -1
  135. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.d.ts +1 -1
  136. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeNode.js.map +1 -1
  137. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js +257 -124
  138. package/lib/esm/tree-widget-react/components/trees/models-tree/internal/ModelsTreeVisibilityHandler.js.map +1 -1
  139. package/lib/esm/tree-widget-react-internal.d.ts +2 -1
  140. package/lib/esm/tree-widget-react-internal.js +2 -1
  141. package/lib/esm/tree-widget-react-internal.js.map +1 -1
  142. package/package.json +1 -1
@@ -2,11 +2,12 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { bufferCount, bufferTime, filter, firstValueFrom, from, map, mergeAll, mergeMap, reduce, ReplaySubject, Subject } from "rxjs";
5
+ import { bufferCount, bufferTime, defaultIfEmpty, defer, EMPTY, filter, firstValueFrom, forkJoin, from, map, mergeAll, mergeMap, reduce, ReplaySubject, shareReplay, Subject, take, tap, toArray, } from "rxjs";
6
6
  import { assert, Guid, Id64 } from "@itwin/core-bentley";
7
7
  import { IModel } from "@itwin/core-common";
8
- import { collect } from "../../common/Rxjs.js";
8
+ import { releaseMainThreadOnItemsCount } from "../../common/internal/Utils.js";
9
9
  import { pushToMap } from "../../common/Utils.js";
10
+ import { getOptimalBatchSize } from "../Utils.js";
10
11
  /** @internal */
11
12
  export class ModelsTreeIdsCache {
12
13
  #categoryElementCounts;
@@ -19,93 +20,221 @@ export class ModelsTreeIdsCache {
19
20
  #categoryKeyPaths;
20
21
  #queryExecutor;
21
22
  #hierarchyConfig;
23
+ #childrenMap;
24
+ /** Stores element ids which have children query scheduled to execute. */
25
+ #childrenLoadingMap;
22
26
  #componentId;
23
27
  #componentName;
24
28
  constructor(queryExecutor, hierarchyConfig, componentId) {
25
29
  this.#hierarchyConfig = hierarchyConfig;
26
30
  this.#queryExecutor = queryExecutor;
27
- this.#categoryElementCounts = new ModelCategoryElementsCountCache(async (input) => this.queryCategoryElementCounts(input));
31
+ this.#categoryElementCounts = new ModelCategoryElementsCountCache((input) => this.queryCategoryElementCounts(input));
28
32
  this.#modelKeyPaths = new Map();
29
33
  this.#subjectKeyPaths = new Map();
30
34
  this.#categoryKeyPaths = new Map();
35
+ this.#childrenMap = new Map();
36
+ this.#childrenLoadingMap = new Map();
31
37
  this.#componentId = componentId ?? Guid.createValue();
32
38
  this.#componentName = "ModelsTreeIdsCache";
33
39
  }
34
40
  [Symbol.dispose]() {
35
41
  this.#categoryElementCounts[Symbol.dispose]();
36
42
  }
37
- async *querySubjects() {
38
- const subjectsQuery = `
39
- SELECT
40
- s.ECInstanceId id,
41
- s.Parent.Id parentId,
42
- (
43
- SELECT m.ECInstanceId
44
- FROM bis.GeometricModel3d m
45
- WHERE m.ECInstanceId = HexToId(json_extract(s.JsonProperties, '$.Subject.Model.TargetPartition'))
46
- AND NOT m.IsPrivate
47
- AND EXISTS (SELECT 1 FROM ${this.#hierarchyConfig.elementClassSpecification} WHERE Model.Id = m.ECInstanceId)
48
- ) targetPartitionId,
49
- CASE
50
- WHEN (
51
- json_extract(s.JsonProperties, '$.Subject.Job.Bridge') IS NOT NULL
52
- OR json_extract(s.JsonProperties, '$.Subject.Model.Type') = 'Hierarchy'
53
- ) THEN 1
54
- ELSE 0
55
- END hideInHierarchy
56
- FROM bis.Subject s
57
- `;
58
- for await (const row of this.#queryExecutor.createQueryReader({ ecsql: subjectsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/subjects` })) {
59
- yield { id: row.id, parentId: row.parentId, targetPartitionId: row.targetPartitionId, hideInHierarchy: !!row.hideInHierarchy };
60
- }
43
+ querySubjects() {
44
+ return defer(() => {
45
+ const subjectsQuery = `
46
+ SELECT
47
+ s.ECInstanceId id,
48
+ s.Parent.Id parentId,
49
+ (
50
+ SELECT m.ECInstanceId
51
+ FROM bis.GeometricModel3d m
52
+ WHERE m.ECInstanceId = HexToId(json_extract(s.JsonProperties, '$.Subject.Model.TargetPartition'))
53
+ AND NOT m.IsPrivate
54
+ AND EXISTS (SELECT 1 FROM ${this.#hierarchyConfig.elementClassSpecification} WHERE Model.Id = m.ECInstanceId)
55
+ ) targetPartitionId,
56
+ CASE
57
+ WHEN (
58
+ json_extract(s.JsonProperties, '$.Subject.Job.Bridge') IS NOT NULL
59
+ OR json_extract(s.JsonProperties, '$.Subject.Model.Type') = 'Hierarchy'
60
+ ) THEN 1
61
+ ELSE 0
62
+ END hideInHierarchy
63
+ FROM bis.Subject s
64
+ `;
65
+ return this.#queryExecutor.createQueryReader({ ecsql: subjectsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/subjects` });
66
+ }).pipe(map((row) => {
67
+ return { id: row.id, parentId: row.parentId, targetPartitionId: row.targetPartitionId, hideInHierarchy: !!row.hideInHierarchy };
68
+ }));
61
69
  }
62
- async *queryModels() {
63
- const modelsQuery = `
70
+ queryModels() {
71
+ return defer(() => {
72
+ const modelsQuery = `
64
73
  SELECT p.ECInstanceId id, p.Parent.Id parentId
65
- FROM bis.InformationPartitionElement p
66
- INNER JOIN bis.GeometricModel3d m ON m.ModeledElement.Id = p.ECInstanceId
67
- WHERE
68
- NOT m.IsPrivate
69
- ${this.#hierarchyConfig.showEmptyModels ? "" : `AND EXISTS (SELECT 1 FROM ${this.#hierarchyConfig.elementClassSpecification} WHERE Model.Id = m.ECInstanceId)`}
70
- `;
71
- for await (const row of this.#queryExecutor.createQueryReader({ ecsql: modelsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/models` })) {
72
- yield { id: row.id, parentId: row.parentId };
74
+ FROM bis.InformationPartitionElement p
75
+ INNER JOIN bis.GeometricModel3d m ON m.ModeledElement.Id = p.ECInstanceId
76
+ WHERE
77
+ NOT m.IsPrivate
78
+ ${this.#hierarchyConfig.showEmptyModels ? "" : `AND EXISTS (SELECT 1 FROM ${this.#hierarchyConfig.elementClassSpecification} WHERE Model.Id = m.ECInstanceId)`}
79
+ `;
80
+ return this.#queryExecutor.createQueryReader({ ecsql: modelsQuery }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/models` });
81
+ }).pipe(map((row) => {
82
+ return { id: row.id, parentId: row.parentId };
83
+ }));
84
+ }
85
+ queryChildren({ elementIds }) {
86
+ if (elementIds.length === 0) {
87
+ return EMPTY;
73
88
  }
89
+ return defer(() => {
90
+ const ctes = [
91
+ `
92
+ ElementChildren(id, parentId) AS (
93
+ SELECT ECInstanceId id, Parent.Id parentId
94
+ FROM ${this.#hierarchyConfig.elementClassSpecification}
95
+ WHERE Parent.Id IN (${elementIds.join(", ")})
96
+
97
+ UNION ALL
98
+
99
+ SELECT c.ECInstanceId id, c.Parent.Id
100
+ FROM ${this.#hierarchyConfig.elementClassSpecification} c
101
+ JOIN ElementChildren p ON c.Parent.Id = p.id
102
+ )
103
+ `,
104
+ ];
105
+ const ecsql = `
106
+ SELECT id, parentId
107
+ FROM ElementChildren
108
+ `;
109
+ return this.#queryExecutor.createQueryReader({ ecsql, ctes }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/children/${Guid.createValue()}` });
110
+ }).pipe(map((row) => {
111
+ return { id: row.id, parentId: row.parentId };
112
+ }));
74
113
  }
75
- async getSubjectInfos() {
76
- this.#subjectInfos ??= (async () => {
77
- const [subjectInfos, targetPartitionSubjects] = await Promise.all([
78
- (async () => {
79
- const result = new Map();
80
- for await (const subject of this.querySubjects()) {
81
- const subjectInfo = {
82
- parentSubject: subject.parentId,
83
- hideInHierarchy: subject.hideInHierarchy,
84
- childSubjects: new Set(),
85
- childModels: new Set(),
86
- };
87
- if (subject.targetPartitionId) {
88
- subjectInfo.childModels.add(subject.targetPartitionId);
89
- }
90
- result.set(subject.id, subjectInfo);
91
- }
92
- return result;
93
- })(),
94
- (async () => {
95
- const result = new Map();
96
- for await (const model of this.queryModels()) {
97
- pushToMap(result, model.id, model.parentId);
114
+ getChildrenTreeFromMap({ elementIds }) {
115
+ const result = new Map();
116
+ if (Id64.sizeOf(elementIds) === 0 || this.#childrenMap.size === 0) {
117
+ return result;
118
+ }
119
+ for (const elementId of Id64.iterable(elementIds)) {
120
+ const entry = this.#childrenMap.get(elementId);
121
+ if (!entry?.children) {
122
+ continue;
123
+ }
124
+ const elementChildrenTree = new Map();
125
+ result.set(elementId, { children: elementChildrenTree });
126
+ entry.children.forEach((childId) => {
127
+ const childrenTreeOfChild = this.getChildrenTreeFromMap({ elementIds: childId });
128
+ // Need to add children tree created from childId. This tree includes childId as root element
129
+ // If child does not have children, children tree won't be created. Need to add childId with undefined children
130
+ elementChildrenTree.set(childId, { children: childrenTreeOfChild.size > 0 ? childrenTreeOfChild : undefined });
131
+ });
132
+ }
133
+ return result;
134
+ }
135
+ getChildrenCountMap({ elementIds }) {
136
+ const result = new Map();
137
+ for (const elementId of Id64.iterable(elementIds)) {
138
+ const entry = this.#childrenMap.get(elementId);
139
+ if (entry?.children) {
140
+ let totalChildrenCount = entry.children.length;
141
+ this.getChildrenCountMap({ elementIds: entry.children }).forEach((childrenOfChildCount) => (totalChildrenCount += childrenOfChildCount));
142
+ result.set(elementId, totalChildrenCount);
143
+ }
144
+ }
145
+ return result;
146
+ }
147
+ /**
148
+ * Populates #childrenLoadingMap with promises. When these promises resolve, they will populate #childrenMap with values and delete entries from #childrenLoadingMap.
149
+ */
150
+ createChildrenLoadingMapEntries({ elementsToQuery }) {
151
+ const getElementsToQueryPromise = async (batchedElementsToQuery) => firstValueFrom(this.queryChildren({ elementIds: batchedElementsToQuery }).pipe(
152
+ // Want to have void at the end instead of void[], so using reduce
153
+ reduce((acc, { parentId, id }) => {
154
+ let entry = this.#childrenMap.get(parentId);
155
+ if (!entry) {
156
+ entry = { children: [] };
157
+ this.#childrenMap.set(parentId, entry);
158
+ }
159
+ if (!entry.children) {
160
+ entry.children = [];
161
+ }
162
+ // Add child to parent's entry and add child to children map
163
+ entry.children.push(id);
164
+ if (!this.#childrenMap.has(id)) {
165
+ this.#childrenMap.set(id, { children: undefined });
166
+ }
167
+ return acc;
168
+ }, (() => { })()), tap({ complete: () => batchedElementsToQuery.forEach((elementId) => this.#childrenLoadingMap.delete(elementId)) }), defaultIfEmpty((() => { })())));
169
+ const maximumBatchSize = 1000;
170
+ const totalSize = elementsToQuery.length;
171
+ const optimalBatchSize = getOptimalBatchSize({ totalSize, maximumBatchSize });
172
+ const loadingMapEntries = new Array();
173
+ // Don't want to slice if its not necessary
174
+ if (totalSize <= maximumBatchSize) {
175
+ loadingMapEntries.push(getElementsToQueryPromise(elementsToQuery));
176
+ }
177
+ else {
178
+ for (let i = 0; i < elementsToQuery.length; i += optimalBatchSize) {
179
+ loadingMapEntries.push(getElementsToQueryPromise(elementsToQuery.slice(i, i + optimalBatchSize)));
180
+ }
181
+ }
182
+ elementsToQuery.forEach((elementId, index) => this.#childrenLoadingMap.set(elementId, loadingMapEntries[Math.floor(index / optimalBatchSize)]));
183
+ return { loadingMapEntries: Promise.all(loadingMapEntries).then(() => { }) };
184
+ }
185
+ createChildrenMapEntries({ elementIds }) {
186
+ return from(Id64.iterable(elementIds)).pipe(reduce((acc, elementId) => {
187
+ if (this.#childrenMap.has(elementId)) {
188
+ return acc;
189
+ }
190
+ const loadingPromise = this.#childrenLoadingMap.get(elementId);
191
+ if (loadingPromise) {
192
+ acc.existingPromises.push(loadingPromise);
193
+ }
194
+ else {
195
+ acc.elementsToQuery.push(elementId);
196
+ }
197
+ return acc;
198
+ }, { existingPromises: new Array(), elementsToQuery: new Array() }), mergeMap(async ({ elementsToQuery, existingPromises }) => {
199
+ existingPromises.push(this.createChildrenLoadingMapEntries({ elementsToQuery }).loadingMapEntries);
200
+ return Promise.all(existingPromises);
201
+ }));
202
+ }
203
+ getChildrenTree({ elementIds }) {
204
+ return this.createChildrenMapEntries({ elementIds }).pipe(map(() => this.getChildrenTreeFromMap({ elementIds })));
205
+ }
206
+ getAllChildrenCount({ elementIds }) {
207
+ return this.createChildrenMapEntries({ elementIds }).pipe(map(() => this.getChildrenCountMap({ elementIds })));
208
+ }
209
+ getSubjectInfos() {
210
+ this.#subjectInfos ??= forkJoin({
211
+ subjectInfos: this.querySubjects().pipe(reduce((acc, subject) => {
212
+ const subjectInfo = {
213
+ parentSubject: subject.parentId,
214
+ hideInHierarchy: subject.hideInHierarchy,
215
+ childSubjects: new Set(),
216
+ childModels: new Set(),
217
+ };
218
+ if (subject.targetPartitionId) {
219
+ subjectInfo.childModels.add(subject.targetPartitionId);
220
+ }
221
+ acc.set(subject.id, subjectInfo);
222
+ return acc;
223
+ }, new Map()), map((subjectInfos) => {
224
+ for (const [subjectId, { parentSubject: parentSubjectId }] of subjectInfos.entries()) {
225
+ if (parentSubjectId) {
226
+ const parentSubjectInfo = subjectInfos.get(parentSubjectId);
227
+ assert(!!parentSubjectInfo);
228
+ parentSubjectInfo.childSubjects.add(subjectId);
98
229
  }
99
- return result;
100
- })(),
101
- ]);
102
- for (const [subjectId, { parentSubject: parentSubjectId }] of subjectInfos.entries()) {
103
- if (parentSubjectId) {
104
- const parentSubjectInfo = subjectInfos.get(parentSubjectId);
105
- assert(!!parentSubjectInfo);
106
- parentSubjectInfo.childSubjects.add(subjectId);
107
230
  }
108
- }
231
+ return subjectInfos;
232
+ })),
233
+ targetPartitionSubjects: this.queryModels().pipe(reduce((acc, model) => {
234
+ pushToMap(acc, model.id, model.parentId);
235
+ return acc;
236
+ }, new Map())),
237
+ }).pipe(map(({ subjectInfos, targetPartitionSubjects }) => {
109
238
  for (const [partitionId, subjectIds] of targetPartitionSubjects) {
110
239
  subjectIds.forEach((subjectId) => {
111
240
  const subjectInfo = subjectInfos.get(subjectId);
@@ -114,13 +243,12 @@ export class ModelsTreeIdsCache {
114
243
  });
115
244
  }
116
245
  return subjectInfos;
117
- })();
246
+ }), shareReplay());
118
247
  return this.#subjectInfos;
119
248
  }
120
249
  /** Returns ECInstanceIDs of Subjects that either have direct Model or at least one child Subject with a Model. */
121
- async getParentSubjectIds() {
122
- this.#parentSubjectIds ??= (async () => {
123
- const subjectInfos = await this.getSubjectInfos();
250
+ getParentSubjectIds() {
251
+ this.#parentSubjectIds ??= this.getSubjectInfos().pipe(map((subjectInfos) => {
124
252
  const parentSubjectIds = new Set();
125
253
  subjectInfos.forEach((subjectInfo, subjectId) => {
126
254
  if (subjectInfo.childModels.size > 0) {
@@ -133,71 +261,73 @@ export class ModelsTreeIdsCache {
133
261
  }
134
262
  });
135
263
  return [...parentSubjectIds];
136
- })();
264
+ }), shareReplay());
137
265
  return this.#parentSubjectIds;
138
266
  }
139
267
  /**
140
268
  * Returns child subjects of the specified parent subjects as they're displayed in the hierarchy - taking into
141
269
  * account `hideInHierarchy` flag.
142
270
  */
143
- async getChildSubjectIds(parentSubjectIds) {
144
- const childSubjectIds = new Array();
145
- const subjectInfos = await this.getSubjectInfos();
146
- parentSubjectIds.forEach((subjectId) => {
147
- forEachChildSubject(subjectInfos, subjectId, (childSubjectId, childSubjectInfo) => {
148
- if (!childSubjectInfo.hideInHierarchy) {
149
- childSubjectIds.push(childSubjectId);
150
- return "break";
151
- }
152
- return "continue";
271
+ getChildSubjectIds(parentSubjectIds) {
272
+ return this.getSubjectInfos().pipe(map((subjectInfos) => {
273
+ const childSubjectIds = new Array();
274
+ parentSubjectIds.forEach((subjectId) => {
275
+ forEachChildSubject(subjectInfos, subjectId, (childSubjectId, childSubjectInfo) => {
276
+ if (!childSubjectInfo.hideInHierarchy) {
277
+ childSubjectIds.push(childSubjectId);
278
+ return "break";
279
+ }
280
+ return "continue";
281
+ });
153
282
  });
154
- });
155
- return childSubjectIds;
283
+ return childSubjectIds;
284
+ }));
156
285
  }
157
286
  /** Returns ECInstanceIDs of all Models under specific parent Subjects, including their child Subjects, etc. */
158
- async getSubjectModelIds(subjectIds) {
159
- const subjectInfos = await this.getSubjectInfos();
160
- const subjectStack = [...subjectIds];
161
- const result = new Array();
162
- while (true) {
163
- const subjectId = subjectStack.pop();
164
- if (subjectId === undefined) {
165
- break;
166
- }
167
- const subjectInfo = subjectInfos.get(subjectId);
168
- if (!subjectInfo) {
169
- continue;
287
+ getSubjectModelIds(subjectIds) {
288
+ return this.getSubjectInfos().pipe(map((subjectInfos) => {
289
+ const subjectStack = [...subjectIds];
290
+ const result = new Array();
291
+ while (true) {
292
+ const subjectId = subjectStack.pop();
293
+ if (subjectId === undefined) {
294
+ break;
295
+ }
296
+ const subjectInfo = subjectInfos.get(subjectId);
297
+ if (!subjectInfo) {
298
+ continue;
299
+ }
300
+ result.push(...subjectInfo.childModels);
301
+ subjectStack.push(...subjectInfo.childSubjects);
170
302
  }
171
- result.push(...subjectInfo.childModels);
172
- subjectStack.push(...subjectInfo.childSubjects);
173
- }
174
- return result;
303
+ return result;
304
+ }));
175
305
  }
176
306
  /** Returns ECInstanceIDs of Models under specific parent Subjects as they are displayed in the hierarchy. */
177
- async getChildSubjectModelIds(parentSubjectIds) {
178
- const subjectInfos = await this.getSubjectInfos();
179
- const hiddenSubjectIds = new Array();
180
- parentSubjectIds.forEach((subjectId) => {
181
- forEachChildSubject(subjectInfos, subjectId, (childSubjectId, childSubjectInfo) => {
182
- if (childSubjectInfo.hideInHierarchy) {
183
- hiddenSubjectIds.push(childSubjectId);
184
- return "continue";
185
- }
186
- return "break";
307
+ getChildSubjectModelIds(parentSubjectIds) {
308
+ return this.getSubjectInfos().pipe(map((subjectInfos) => {
309
+ const hiddenSubjectIds = new Array();
310
+ parentSubjectIds.forEach((subjectId) => {
311
+ forEachChildSubject(subjectInfos, subjectId, (childSubjectId, childSubjectInfo) => {
312
+ if (childSubjectInfo.hideInHierarchy) {
313
+ hiddenSubjectIds.push(childSubjectId);
314
+ return "continue";
315
+ }
316
+ return "break";
317
+ });
187
318
  });
188
- });
189
- const modelIds = new Array();
190
- [...parentSubjectIds, ...hiddenSubjectIds].forEach((subjectId) => {
191
- const subjectInfo = subjectInfos.get(subjectId);
192
- subjectInfo && modelIds.push(...subjectInfo.childModels);
193
- });
194
- return modelIds;
319
+ const modelIds = new Array();
320
+ [...parentSubjectIds, ...hiddenSubjectIds].forEach((subjectId) => {
321
+ const subjectInfo = subjectInfos.get(subjectId);
322
+ subjectInfo && modelIds.push(...subjectInfo.childModels);
323
+ });
324
+ return modelIds;
325
+ }));
195
326
  }
196
- async createSubjectInstanceKeysPath(targetSubjectId) {
327
+ createSubjectInstanceKeysPath(targetSubjectId) {
197
328
  let entry = this.#subjectKeyPaths.get(targetSubjectId);
198
329
  if (!entry) {
199
- entry = (async () => {
200
- const subjectInfos = await this.getSubjectInfos();
330
+ entry = this.getSubjectInfos().pipe(map((subjectInfos) => {
201
331
  const result = new Array();
202
332
  let currParentId = targetSubjectId;
203
333
  while (currParentId) {
@@ -211,128 +341,111 @@ export class ModelsTreeIdsCache {
211
341
  currParentId = parentInfo?.parentSubject;
212
342
  }
213
343
  return result.reverse();
214
- })();
344
+ }), shareReplay());
215
345
  this.#subjectKeyPaths.set(targetSubjectId, entry);
216
346
  }
217
347
  return entry;
218
348
  }
219
- async *queryModelCategories() {
220
- const query = `
221
- SELECT
222
- this.Model.Id modelId,
223
- this.Category.Id categoryId,
224
- m.IsPrivate isModelPrivate,
225
- MAX(IIF(this.Parent.Id IS NULL, 1, 0)) isRootElementCategory
226
- FROM BisCore.Model m
227
- JOIN ${this.#hierarchyConfig.elementClassSpecification} this ON m.ECInstanceId = this.Model.Id
228
- GROUP BY modelId, categoryId, isModelPrivate
229
- `;
230
- for await (const row of this.#queryExecutor.createQueryReader({ ecsql: query }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/model-categories` })) {
231
- yield { modelId: row.modelId, categoryId: row.categoryId, isModelPrivate: !!row.isModelPrivate, isRootElementCategory: !!row.isRootElementCategory };
232
- }
349
+ queryModelCategories() {
350
+ return defer(() => {
351
+ const query = `
352
+ SELECT
353
+ this.Model.Id modelId,
354
+ this.Category.Id categoryId,
355
+ m.IsPrivate isModelPrivate,
356
+ MAX(IIF(this.Parent.Id IS NULL, 1, 0)) isRootElementCategory
357
+ FROM BisCore.Model m
358
+ JOIN ${this.#hierarchyConfig.elementClassSpecification} this ON m.ECInstanceId = this.Model.Id
359
+ GROUP BY modelId, categoryId, isModelPrivate
360
+ `;
361
+ return this.#queryExecutor.createQueryReader({ ecsql: query }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/model-categories` });
362
+ }).pipe(map((row) => {
363
+ return { modelId: row.modelId, categoryId: row.categoryId, isModelPrivate: !!row.isModelPrivate, isRootElementCategory: !!row.isRootElementCategory };
364
+ }));
233
365
  }
234
- async *queryModeledElements() {
235
- const query = `
236
- SELECT
237
- pe.ECInstanceId modeledElementId,
238
- pe.Category.Id categoryId,
239
- pe.Model.Id modelId
240
- FROM BisCore.Model m
241
- JOIN ${this.#hierarchyConfig.elementClassSpecification} pe ON pe.ECInstanceId = m.ModeledElement.Id
242
- WHERE
243
- m.IsPrivate = false
244
- AND m.ECInstanceId IN (SELECT Model.Id FROM ${this.#hierarchyConfig.elementClassSpecification})
245
- `;
246
- for await (const row of this.#queryExecutor.createQueryReader({ ecsql: query }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/modeled-elements` })) {
247
- yield { modelId: row.modelId, categoryId: row.categoryId, modeledElementId: row.modeledElementId };
248
- }
366
+ queryModeledElements() {
367
+ return defer(() => {
368
+ const query = `
369
+ SELECT
370
+ pe.ECInstanceId modeledElementId,
371
+ pe.Category.Id categoryId,
372
+ pe.Model.Id modelId
373
+ FROM BisCore.Model m
374
+ JOIN ${this.#hierarchyConfig.elementClassSpecification} pe ON pe.ECInstanceId = m.ModeledElement.Id
375
+ WHERE
376
+ m.IsPrivate = false
377
+ AND m.ECInstanceId IN (SELECT Model.Id FROM ${this.#hierarchyConfig.elementClassSpecification})
378
+ `;
379
+ return this.#queryExecutor.createQueryReader({ ecsql: query }, { rowFormat: "ECSqlPropertyNames", limit: "unbounded", restartToken: `${this.#componentName}/${this.#componentId}/modeled-elements` });
380
+ }).pipe(map((row) => {
381
+ return { modelId: row.modelId, categoryId: row.categoryId, modeledElementId: row.modeledElementId };
382
+ }));
249
383
  }
250
- async getModelWithCategoryModeledElements() {
251
- this.#modelWithCategoryModeledElements ??= (async () => {
252
- const modelWithCategoryModeledElements = new Map();
253
- for await (const { modelId, categoryId, modeledElementId } of this.queryModeledElements()) {
254
- const key = `${modelId}-${categoryId}`;
255
- const entry = modelWithCategoryModeledElements.get(key);
256
- if (entry === undefined) {
257
- modelWithCategoryModeledElements.set(key, new Set([modeledElementId]));
258
- }
259
- else {
260
- entry.add(modeledElementId);
261
- }
384
+ getModelWithCategoryModeledElements() {
385
+ this.#modelWithCategoryModeledElements ??= this.queryModeledElements().pipe(reduce((acc, { modelId, categoryId, modeledElementId }) => {
386
+ const key = `${modelId}-${categoryId}`;
387
+ const entry = acc.get(key);
388
+ if (entry === undefined) {
389
+ acc.set(key, new Set([modeledElementId]));
262
390
  }
263
- return modelWithCategoryModeledElements;
264
- })();
391
+ else {
392
+ entry.add(modeledElementId);
393
+ }
394
+ return acc;
395
+ }, new Map()), shareReplay());
265
396
  return this.#modelWithCategoryModeledElements;
266
397
  }
267
- async getModelInfos() {
268
- this.#modelInfos ??= (async () => {
269
- const modelInfos = new Map();
270
- for await (const { modelId, categoryId, isModelPrivate, isRootElementCategory } of this.queryModelCategories()) {
271
- const entry = modelInfos.get(modelId);
272
- if (entry) {
273
- entry.categories.set(categoryId, { isRootElementCategory });
274
- entry.isModelPrivate = isModelPrivate;
275
- }
276
- else {
277
- modelInfos.set(modelId, { categories: new Map([[categoryId, { isRootElementCategory }]]), isModelPrivate });
278
- }
398
+ getModelInfos() {
399
+ this.#modelInfos ??= this.queryModelCategories().pipe(reduce((acc, { modelId, categoryId, isModelPrivate, isRootElementCategory }) => {
400
+ const entry = acc.get(modelId);
401
+ if (entry) {
402
+ entry.categories.set(categoryId, { isRootElementCategory });
403
+ entry.isModelPrivate = isModelPrivate;
404
+ }
405
+ else {
406
+ acc.set(modelId, { categories: new Map([[categoryId, { isRootElementCategory }]]), isModelPrivate });
279
407
  }
280
- return modelInfos;
281
- })();
408
+ return acc;
409
+ }, new Map()), shareReplay());
282
410
  return this.#modelInfos;
283
411
  }
284
- async getAllCategories() {
285
- const modelInfos = await this.getModelInfos();
286
- const result = new Set();
287
- modelInfos.forEach(({ categories }) => {
288
- categories.forEach((_, categoryId) => result.add(categoryId));
289
- });
290
- return result;
412
+ getAllCategories() {
413
+ return this.getModelInfos().pipe(mergeMap((modelInfos) => modelInfos.values()), mergeMap(({ categories }) => categories.keys()), reduce((acc, categoryId) => {
414
+ acc.add(categoryId);
415
+ return acc;
416
+ }, new Set()));
291
417
  }
292
- async getModelCategories(modelId) {
293
- const modelInfos = await this.getModelInfos();
294
- const categories = modelInfos.get(modelId)?.categories.keys();
295
- return categories ? [...categories] : [];
418
+ getModelCategories(modelId) {
419
+ return this.getModelInfos().pipe(mergeMap((modelInfos) => modelInfos.get(modelId)?.categories.keys() ?? []), toArray());
296
420
  }
297
- async hasSubModel(elementId) {
298
- const modelInfos = await this.getModelInfos();
299
- const modeledElementInfo = modelInfos.get(elementId);
300
- if (!modeledElementInfo) {
301
- return false;
302
- }
303
- return !modeledElementInfo.isModelPrivate;
421
+ hasSubModel(elementId) {
422
+ return this.getModelInfos().pipe(map((modelInfos) => {
423
+ const modeledElementInfo = modelInfos.get(elementId);
424
+ if (!modeledElementInfo) {
425
+ return false;
426
+ }
427
+ return !modeledElementInfo.isModelPrivate;
428
+ }));
304
429
  }
305
- async getCategoriesModeledElements(modelId, categoryIds) {
306
- const modelWithCategoryModeledElements = await this.getModelWithCategoryModeledElements();
307
- const result = new Array();
308
- for (const categoryId of Id64.iterable(categoryIds)) {
430
+ getCategoriesModeledElements(modelId, categoryIds) {
431
+ return this.getModelWithCategoryModeledElements().pipe(mergeMap((modelWithCategoryModeledElements) => from(Id64.iterable(categoryIds)).pipe(reduce((acc, categoryId) => {
309
432
  const entry = modelWithCategoryModeledElements.get(`${modelId}-${categoryId}`);
310
433
  if (entry !== undefined) {
311
- result.push(...entry);
434
+ acc.push(...entry);
312
435
  }
313
- }
314
- return result;
436
+ return acc;
437
+ }, new Array()))));
315
438
  }
316
- async createModelInstanceKeyPaths(modelId) {
439
+ createModelInstanceKeyPaths(modelId) {
317
440
  let entry = this.#modelKeyPaths.get(modelId);
318
441
  if (!entry) {
319
- entry = (async () => {
320
- const result = new Array();
321
- const subjectInfos = (await this.getSubjectInfos()).entries();
322
- for (const [modelSubjectId, subjectInfo] of subjectInfos) {
323
- if (subjectInfo.childModels.has(modelId)) {
324
- const subjectPath = await this.createSubjectInstanceKeysPath(modelSubjectId);
325
- result.push([...subjectPath, { className: "BisCore.GeometricModel3d", id: modelId }]);
326
- }
327
- }
328
- return result;
329
- })();
442
+ entry = this.getSubjectInfos().pipe(mergeMap((subjectInfos) => subjectInfos.entries()), filter(([_, subjectInfo]) => subjectInfo.childModels.has(modelId)), mergeMap(([modelSubjectId]) => this.createSubjectInstanceKeysPath(modelSubjectId).pipe(map((path) => [...path, { className: "BisCore.GeometricModel3d", id: modelId }]))), toArray(), shareReplay());
330
443
  this.#modelKeyPaths.set(modelId, entry);
331
444
  }
332
445
  return entry;
333
446
  }
334
- async queryCategoryElementCounts(input) {
335
- return collect(from(input).pipe(reduce((acc, { modelId, categoryId }) => {
447
+ queryCategoryElementCounts(input) {
448
+ return from(input).pipe(reduce((acc, { modelId, categoryId }) => {
336
449
  const entry = acc.get(modelId);
337
450
  if (!entry) {
338
451
  acc.set(modelId, new Set([categoryId]));
@@ -344,73 +457,64 @@ export class ModelsTreeIdsCache {
344
457
  }, new Map()), mergeMap((modelCategoryMap) => modelCategoryMap.entries()), map(([modelId, categoryIds]) => `Model.Id = ${modelId} AND Category.Id IN (${[...categoryIds].join(", ")})`),
345
458
  // we may have thousands of where clauses here, and sending a single query with all of them could take a
346
459
  // long time - instead, split it into smaller chunks
347
- bufferCount(100), mergeMap(async (whereClauses) => {
348
- const reader = this.#queryExecutor.createQueryReader({
349
- ctes: [
350
- `
351
- CategoryElements(id, modelId, categoryId) AS (
352
- SELECT ECInstanceId, Model.Id, Category.Id
353
- FROM ${this.#hierarchyConfig.elementClassSpecification}
354
- WHERE
355
- Parent.Id IS NULL
356
- AND (
357
- ${whereClauses.join(" OR ")}
358
- )
460
+ bufferCount(100), mergeMap((whereClauses) => defer(() => this.#queryExecutor.createQueryReader({
461
+ ctes: [
462
+ `
463
+ CategoryElements(id, modelId, categoryId) AS (
464
+ SELECT ECInstanceId, Model.Id, Category.Id
465
+ FROM ${this.#hierarchyConfig.elementClassSpecification}
466
+ WHERE
467
+ Parent.Id IS NULL
468
+ AND (
469
+ ${whereClauses.join(" OR ")}
470
+ )
359
471
 
360
- UNION ALL
472
+ UNION ALL
361
473
 
362
- SELECT c.ECInstanceId, p.modelId, p.categoryId
363
- FROM ${this.#hierarchyConfig.elementClassSpecification} c
364
- JOIN CategoryElements p ON c.Parent.Id = p.id
365
- )
474
+ SELECT c.ECInstanceId, p.modelId, p.categoryId
475
+ FROM ${this.#hierarchyConfig.elementClassSpecification} c
476
+ JOIN CategoryElements p ON c.Parent.Id = p.id
477
+ )
478
+ `,
479
+ ],
480
+ ecsql: `
481
+ SELECT modelId, categoryId, COUNT(id) elementsCount
482
+ FROM CategoryElements
483
+ GROUP BY modelId, categoryId
366
484
  `,
367
- ],
368
- ecsql: `
369
- SELECT modelId, categoryId, COUNT(id) elementsCount
370
- FROM CategoryElements
371
- GROUP BY modelId, categoryId
372
- `,
373
- }, {
374
- rowFormat: "ECSqlPropertyNames",
375
- limit: "unbounded",
376
- restartToken: `${this.#componentName}/${this.#componentId}/category-element-counts/${Guid.createValue()}`,
485
+ }, {
486
+ rowFormat: "ECSqlPropertyNames",
487
+ limit: "unbounded",
488
+ restartToken: `${this.#componentName}/${this.#componentId}/category-element-counts/${Guid.createValue()}`,
489
+ }))), releaseMainThreadOnItemsCount(500), reduce(({ acc, createKey }, row) => {
490
+ acc.set(createKey({ modelId: row.modelId, categoryId: row.categoryId }), {
491
+ modelId: row.modelId,
492
+ categoryId: row.categoryId,
493
+ elementsCount: row.elementsCount,
377
494
  });
378
- const result = new Array();
379
- for await (const row of reader) {
380
- result.push({ modelId: row.modelId, categoryId: row.categoryId, elementsCount: row.elementsCount });
381
- }
495
+ return { acc, createKey };
496
+ }, {
497
+ acc: new Map(),
498
+ createKey: (keyProps) => `${keyProps.modelId}-${keyProps.categoryId}`,
499
+ }), mergeMap(({ acc: result, createKey }) => {
382
500
  input.forEach(({ modelId, categoryId }) => {
383
- if (!result.some((queriedResult) => queriedResult.categoryId === categoryId && queriedResult.modelId === modelId)) {
384
- result.push({ categoryId, modelId, elementsCount: 0 });
501
+ if (!result.has(createKey({ modelId, categoryId }))) {
502
+ result.set(createKey({ modelId, categoryId }), { categoryId, modelId, elementsCount: 0 });
385
503
  }
386
504
  });
387
- return result;
388
- }), mergeAll()));
505
+ return from(result.values());
506
+ }), toArray());
389
507
  }
390
- async getCategoryElementsCount(modelId, categoryId) {
508
+ getCategoryElementsCount(modelId, categoryId) {
391
509
  return this.#categoryElementCounts.getCategoryElementsCount(modelId, categoryId);
392
510
  }
393
- async createCategoryInstanceKeyPaths(categoryId) {
511
+ createCategoryInstanceKeyPaths(categoryId) {
394
512
  let entry = this.#categoryKeyPaths.get(categoryId);
395
513
  if (!entry) {
396
- entry = (async () => {
397
- const result = new Set();
398
- const modelInfos = await this.getModelInfos();
399
- modelInfos?.forEach((modelInfo, modelId) => {
400
- const categoryEntry = modelInfo.categories.get(categoryId);
401
- if (categoryEntry?.isRootElementCategory) {
402
- result.add(modelId);
403
- }
404
- });
405
- const categoryPaths = new Array();
406
- for (const categoryModelId of [...result]) {
407
- const modelPaths = await this.createModelInstanceKeyPaths(categoryModelId);
408
- for (const modelPath of modelPaths) {
409
- categoryPaths.push([...modelPath, { className: "BisCore.SpatialCategory", id: categoryId }]);
410
- }
411
- }
412
- return categoryPaths;
413
- })();
514
+ entry = this.getModelInfos().pipe(mergeMap((modelInfos) => modelInfos.entries()), filter(([_, modelInfo]) => !!modelInfo.categories.get(categoryId)?.isRootElementCategory), mergeMap(([categoryModelId]) => this.createModelInstanceKeyPaths(categoryModelId)), mergeMap((modelPaths) => modelPaths), reduce((acc, modelPath) => {
515
+ acc.push([...modelPath, { className: "BisCore.SpatialCategory", id: categoryId }]);
516
+ return acc;
517
+ }, new Array()), shareReplay());
414
518
  this.#categoryKeyPaths.set(categoryId, entry);
415
519
  }
416
520
  return entry;
@@ -433,7 +537,7 @@ class ModelCategoryElementsCountCache {
433
537
  #subscription;
434
538
  constructor(loader) {
435
539
  this.#subscription = this.#requestsStream
436
- .pipe(bufferTime(20), filter((requests) => requests.length > 0), mergeMap(async (requests) => loader(requests)), mergeAll())
540
+ .pipe(bufferTime(20), filter((requests) => requests.length > 0), mergeMap((requests) => loader(requests)), mergeAll())
437
541
  .subscribe({
438
542
  next: ({ modelId, categoryId, elementsCount }) => {
439
543
  const subject = this.#cache.get(`${modelId}${categoryId}`);
@@ -445,16 +549,16 @@ class ModelCategoryElementsCountCache {
445
549
  [Symbol.dispose]() {
446
550
  this.#subscription.unsubscribe();
447
551
  }
448
- async getCategoryElementsCount(modelId, categoryId) {
552
+ getCategoryElementsCount(modelId, categoryId) {
449
553
  const cacheKey = `${modelId}${categoryId}`;
450
554
  let result = this.#cache.get(cacheKey);
451
555
  if (result !== undefined) {
452
- return firstValueFrom(result);
556
+ return from(result).pipe(take(1));
453
557
  }
454
558
  result = new ReplaySubject(1);
455
559
  this.#cache.set(cacheKey, result);
456
560
  this.#requestsStream.next({ modelId, categoryId });
457
- return firstValueFrom(result);
561
+ return from(result).pipe(take(1));
458
562
  }
459
563
  }
460
564
  //# sourceMappingURL=ModelsTreeIdsCache.js.map