@matter/model 0.11.9-alpha.0-20241203-06077d82e → 0.11.9-alpha.0-20241206-22f233334

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 (182) hide show
  1. package/dist/cjs/elements/MatterElement.d.ts +5 -1
  2. package/dist/cjs/elements/MatterElement.d.ts.map +1 -1
  3. package/dist/cjs/elements/MatterElement.js +1 -1
  4. package/dist/cjs/elements/MatterElement.js.map +1 -1
  5. package/dist/cjs/logic/DefaultValue.d.ts +3 -1
  6. package/dist/cjs/logic/DefaultValue.d.ts.map +1 -1
  7. package/dist/cjs/logic/DefaultValue.js +11 -11
  8. package/dist/cjs/logic/DefaultValue.js.map +1 -1
  9. package/dist/cjs/logic/ModelTraversal.d.ts +6 -35
  10. package/dist/cjs/logic/ModelTraversal.d.ts.map +1 -1
  11. package/dist/cjs/logic/ModelTraversal.js +15 -109
  12. package/dist/cjs/logic/ModelTraversal.js.map +1 -1
  13. package/dist/cjs/logic/ModelVariantTraversal.d.ts.map +1 -1
  14. package/dist/cjs/logic/ModelVariantTraversal.js.map +1 -1
  15. package/dist/cjs/logic/Scope.d.ts +102 -0
  16. package/dist/cjs/logic/Scope.d.ts.map +1 -0
  17. package/dist/cjs/logic/Scope.js +220 -0
  18. package/dist/cjs/logic/Scope.js.map +6 -0
  19. package/dist/cjs/logic/definition-validation/ModelValidator.d.ts.map +1 -1
  20. package/dist/cjs/logic/index.d.ts +1 -0
  21. package/dist/cjs/logic/index.d.ts.map +1 -1
  22. package/dist/cjs/logic/index.js +1 -0
  23. package/dist/cjs/logic/index.js.map +1 -1
  24. package/dist/cjs/models/AttributeModel.d.ts +20 -0
  25. package/dist/cjs/models/AttributeModel.d.ts.map +1 -1
  26. package/dist/cjs/models/AttributeModel.js +3 -0
  27. package/dist/cjs/models/AttributeModel.js.map +1 -1
  28. package/dist/cjs/models/Children.d.ts +4 -0
  29. package/dist/cjs/models/Children.d.ts.map +1 -1
  30. package/dist/cjs/models/Children.js +13 -0
  31. package/dist/cjs/models/Children.js.map +1 -1
  32. package/dist/cjs/models/ClusterModel.d.ts +6 -7
  33. package/dist/cjs/models/ClusterModel.d.ts.map +1 -1
  34. package/dist/cjs/models/ClusterModel.js +5 -38
  35. package/dist/cjs/models/ClusterModel.js.map +1 -1
  36. package/dist/cjs/models/CommandModel.d.ts +22 -0
  37. package/dist/cjs/models/CommandModel.d.ts.map +1 -1
  38. package/dist/cjs/models/CommandModel.js +3 -0
  39. package/dist/cjs/models/CommandModel.js.map +1 -1
  40. package/dist/cjs/models/DatatypeModel.js +0 -2
  41. package/dist/cjs/models/DatatypeModel.js.map +1 -1
  42. package/dist/cjs/models/EventModel.d.ts +21 -0
  43. package/dist/cjs/models/EventModel.d.ts.map +1 -1
  44. package/dist/cjs/models/EventModel.js +3 -0
  45. package/dist/cjs/models/EventModel.js.map +1 -1
  46. package/dist/cjs/models/MatterModel.d.ts +3 -3
  47. package/dist/cjs/models/MatterModel.d.ts.map +1 -1
  48. package/dist/cjs/models/MatterModel.js +6 -5
  49. package/dist/cjs/models/MatterModel.js.map +1 -1
  50. package/dist/cjs/models/Model.d.ts +10 -6
  51. package/dist/cjs/models/Model.d.ts.map +1 -1
  52. package/dist/cjs/models/Model.js +28 -9
  53. package/dist/cjs/models/Model.js.map +1 -1
  54. package/dist/cjs/models/ScopeModel.d.ts +22 -0
  55. package/dist/cjs/models/ScopeModel.d.ts.map +1 -0
  56. package/dist/cjs/models/ScopeModel.js +63 -0
  57. package/dist/cjs/models/ScopeModel.js.map +6 -0
  58. package/dist/cjs/models/ValueModel.d.ts +7 -21
  59. package/dist/cjs/models/ValueModel.d.ts.map +1 -1
  60. package/dist/cjs/models/ValueModel.js +15 -34
  61. package/dist/cjs/models/ValueModel.js.map +1 -1
  62. package/dist/cjs/models/index.d.ts +1 -0
  63. package/dist/cjs/models/index.d.ts.map +1 -1
  64. package/dist/cjs/models/index.js +1 -0
  65. package/dist/cjs/models/index.js.map +1 -1
  66. package/dist/cjs/standard/elements/ColorControl.js +3 -4
  67. package/dist/cjs/standard/elements/ColorControl.js.map +1 -1
  68. package/dist/cjs/standard/elements/ModeBase.js +1 -1
  69. package/dist/cjs/standard/elements/ModeBase.js.map +1 -1
  70. package/dist/cjs/standard/elements/PumpConfigurationAndControl.d.ts.map +1 -1
  71. package/dist/cjs/standard/elements/PumpConfigurationAndControl.js +3 -20
  72. package/dist/cjs/standard/elements/PumpConfigurationAndControl.js.map +1 -1
  73. package/dist/cjs/standard/elements/UserLabel.d.ts.map +1 -1
  74. package/dist/cjs/standard/elements/UserLabel.js +13 -16
  75. package/dist/cjs/standard/elements/UserLabel.js.map +1 -1
  76. package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.d.ts.map +1 -1
  77. package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.js +1 -5
  78. package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.js.map +1 -1
  79. package/dist/cjs/standard/elements/WindowCovering.js +2 -2
  80. package/dist/esm/elements/MatterElement.d.ts +5 -1
  81. package/dist/esm/elements/MatterElement.d.ts.map +1 -1
  82. package/dist/esm/elements/MatterElement.js +1 -1
  83. package/dist/esm/elements/MatterElement.js.map +1 -1
  84. package/dist/esm/logic/DefaultValue.d.ts +3 -1
  85. package/dist/esm/logic/DefaultValue.d.ts.map +1 -1
  86. package/dist/esm/logic/DefaultValue.js +11 -11
  87. package/dist/esm/logic/DefaultValue.js.map +1 -1
  88. package/dist/esm/logic/ModelTraversal.d.ts +6 -35
  89. package/dist/esm/logic/ModelTraversal.d.ts.map +1 -1
  90. package/dist/esm/logic/ModelTraversal.js +16 -110
  91. package/dist/esm/logic/ModelTraversal.js.map +1 -1
  92. package/dist/esm/logic/ModelVariantTraversal.d.ts.map +1 -1
  93. package/dist/esm/logic/ModelVariantTraversal.js.map +1 -1
  94. package/dist/esm/logic/Scope.d.ts +102 -0
  95. package/dist/esm/logic/Scope.d.ts.map +1 -0
  96. package/dist/esm/logic/Scope.js +200 -0
  97. package/dist/esm/logic/Scope.js.map +6 -0
  98. package/dist/esm/logic/definition-validation/ModelValidator.d.ts.map +1 -1
  99. package/dist/esm/logic/index.d.ts +1 -0
  100. package/dist/esm/logic/index.d.ts.map +1 -1
  101. package/dist/esm/logic/index.js +1 -0
  102. package/dist/esm/logic/index.js.map +1 -1
  103. package/dist/esm/models/AttributeModel.d.ts +20 -0
  104. package/dist/esm/models/AttributeModel.d.ts.map +1 -1
  105. package/dist/esm/models/AttributeModel.js +3 -0
  106. package/dist/esm/models/AttributeModel.js.map +1 -1
  107. package/dist/esm/models/Children.d.ts +4 -0
  108. package/dist/esm/models/Children.d.ts.map +1 -1
  109. package/dist/esm/models/Children.js +13 -0
  110. package/dist/esm/models/Children.js.map +1 -1
  111. package/dist/esm/models/ClusterModel.d.ts +6 -7
  112. package/dist/esm/models/ClusterModel.d.ts.map +1 -1
  113. package/dist/esm/models/ClusterModel.js +5 -38
  114. package/dist/esm/models/ClusterModel.js.map +1 -1
  115. package/dist/esm/models/CommandModel.d.ts +22 -0
  116. package/dist/esm/models/CommandModel.d.ts.map +1 -1
  117. package/dist/esm/models/CommandModel.js +3 -0
  118. package/dist/esm/models/CommandModel.js.map +1 -1
  119. package/dist/esm/models/DatatypeModel.js +0 -2
  120. package/dist/esm/models/DatatypeModel.js.map +1 -1
  121. package/dist/esm/models/EventModel.d.ts +21 -0
  122. package/dist/esm/models/EventModel.d.ts.map +1 -1
  123. package/dist/esm/models/EventModel.js +3 -0
  124. package/dist/esm/models/EventModel.js.map +1 -1
  125. package/dist/esm/models/MatterModel.d.ts +3 -3
  126. package/dist/esm/models/MatterModel.d.ts.map +1 -1
  127. package/dist/esm/models/MatterModel.js +6 -5
  128. package/dist/esm/models/MatterModel.js.map +1 -1
  129. package/dist/esm/models/Model.d.ts +10 -6
  130. package/dist/esm/models/Model.d.ts.map +1 -1
  131. package/dist/esm/models/Model.js +28 -9
  132. package/dist/esm/models/Model.js.map +1 -1
  133. package/dist/esm/models/ScopeModel.d.ts +22 -0
  134. package/dist/esm/models/ScopeModel.d.ts.map +1 -0
  135. package/dist/esm/models/ScopeModel.js +43 -0
  136. package/dist/esm/models/ScopeModel.js.map +6 -0
  137. package/dist/esm/models/ValueModel.d.ts +7 -21
  138. package/dist/esm/models/ValueModel.d.ts.map +1 -1
  139. package/dist/esm/models/ValueModel.js +15 -34
  140. package/dist/esm/models/ValueModel.js.map +1 -1
  141. package/dist/esm/models/index.d.ts +1 -0
  142. package/dist/esm/models/index.d.ts.map +1 -1
  143. package/dist/esm/models/index.js +1 -0
  144. package/dist/esm/models/index.js.map +1 -1
  145. package/dist/esm/standard/elements/ColorControl.js +3 -4
  146. package/dist/esm/standard/elements/ColorControl.js.map +1 -1
  147. package/dist/esm/standard/elements/ModeBase.js +1 -1
  148. package/dist/esm/standard/elements/ModeBase.js.map +1 -1
  149. package/dist/esm/standard/elements/PumpConfigurationAndControl.d.ts.map +1 -1
  150. package/dist/esm/standard/elements/PumpConfigurationAndControl.js +3 -20
  151. package/dist/esm/standard/elements/PumpConfigurationAndControl.js.map +1 -1
  152. package/dist/esm/standard/elements/UserLabel.d.ts.map +1 -1
  153. package/dist/esm/standard/elements/UserLabel.js +13 -16
  154. package/dist/esm/standard/elements/UserLabel.js.map +1 -1
  155. package/dist/esm/standard/elements/WildcardPathFlagsBitmap.d.ts.map +1 -1
  156. package/dist/esm/standard/elements/WildcardPathFlagsBitmap.js +1 -5
  157. package/dist/esm/standard/elements/WildcardPathFlagsBitmap.js.map +1 -1
  158. package/dist/esm/standard/elements/WindowCovering.js +2 -2
  159. package/package.json +4 -4
  160. package/src/elements/MatterElement.ts +7 -2
  161. package/src/logic/DefaultValue.ts +13 -11
  162. package/src/logic/ModelTraversal.ts +28 -147
  163. package/src/logic/ModelVariantTraversal.ts +0 -1
  164. package/src/logic/Scope.ts +400 -0
  165. package/src/logic/index.ts +1 -0
  166. package/src/models/AttributeModel.ts +4 -0
  167. package/src/models/Children.ts +24 -1
  168. package/src/models/ClusterModel.ts +12 -51
  169. package/src/models/CommandModel.ts +4 -0
  170. package/src/models/DatatypeModel.ts +0 -2
  171. package/src/models/EventModel.ts +4 -0
  172. package/src/models/MatterModel.ts +6 -5
  173. package/src/models/Model.ts +38 -12
  174. package/src/models/ScopeModel.ts +55 -0
  175. package/src/models/ValueModel.ts +17 -39
  176. package/src/models/index.ts +1 -0
  177. package/src/standard/elements/ColorControl.ts +4 -4
  178. package/src/standard/elements/ModeBase.ts +1 -1
  179. package/src/standard/elements/PumpConfigurationAndControl.ts +2 -18
  180. package/src/standard/elements/UserLabel.ts +8 -12
  181. package/src/standard/elements/WildcardPathFlagsBitmap.ts +1 -4
  182. package/src/standard/elements/WindowCovering.ts +2 -2
@@ -0,0 +1,400 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ElementTag } from "#common/ElementTag.js";
8
+ import { SchemaImplementationError } from "#common/errors.js";
9
+ import { FeatureSet } from "#common/FeatureSet.js";
10
+ import { ImplementationError } from "@matter/general";
11
+ import { ModelTraversal } from "./ModelTraversal.js";
12
+
13
+ // These must be types to avoid circular references
14
+ import type { ClusterModel, Model, ScopeModel, ValueModel } from "#models/index.js";
15
+
16
+ const DEFAULT_TAGS = new Set([ElementTag.Field, ElementTag.Attribute]);
17
+ const GLOBAL_IDS = new Set([0xfffd, 0xfffc, 0xfffb, 0xfffa, 0xfff9, 0xfff8]);
18
+
19
+ const cache = new WeakMap<Model, Scope>();
20
+
21
+ /**
22
+ * Tracks extensions for a scope to models in parent scopes.
23
+ *
24
+ * A child model with the same {tag, name, type} tuple in a derived scope is a semantic extension of the model in the
25
+ * parent scope even if it does not explicitly inherit from the parent via {@link Model#type}. We refer to the implicit
26
+ * base class as a *shadow* and the implicit child class as an *extension*.
27
+ *
28
+ * Note that "type" in above tuple includes either undefined, the model's own name, or extension of the exact same type
29
+ * in both the shadow and the extension. In the former case the shadow is implicit; in the latter case it is explicit.
30
+ *
31
+ * This utility provideds optimized lookup of extensions present in a particular scope.
32
+ *
33
+ * If the scope is frozen the analysis is cached.
34
+ *
35
+ * TODO - there is remaining ambiguity in shadow selection, fine for now but could be eliminated with conformance
36
+ *
37
+ * TODO - currently we only consider shadows at scope root but shadows of nested children is possible with this approach
38
+ */
39
+ export interface Scope {
40
+ /**
41
+ * The model analyzed.
42
+ */
43
+ owner: Model;
44
+
45
+ /**
46
+ * Determine if the model is a shadow.
47
+ */
48
+ isShadow(model?: Model): boolean;
49
+
50
+ /**
51
+ * Retrieve the extension for an element if it is a shadow.
52
+ */
53
+ extensionOf<T extends Model>(model?: T): undefined | T;
54
+
55
+ /**
56
+ * Obtain canonical definition for model. If the input model is a shadow returns the extension, otherwise returns
57
+ * the input model.
58
+ */
59
+ modelFor<T extends Model>(model: T): T;
60
+
61
+ /**
62
+ * Identify members (child properties) of the designated model in this scope.
63
+ */
64
+ membersOf<T extends Model>(parent: T, options?: Scope.MemberOptions): Model.ChildOf<T>[];
65
+ }
66
+
67
+ /**
68
+ * Obtain the scope for a model.
69
+ *
70
+ * By default, if {@link subject} is not a {@link ScopeModel} the scope returned is for the nearest owning
71
+ * {@link ScopeModel}.
72
+ */
73
+ export function Scope(subject: Model, options: Scope.ScopeOptions = {}) {
74
+ let owner: Model;
75
+
76
+ if ((subject as ScopeModel).isScope || options.forceOwner) {
77
+ owner = subject as ScopeModel;
78
+ } else {
79
+ const scope = new ModelTraversal().findScope(subject);
80
+
81
+ if (scope === undefined) {
82
+ throw new SchemaImplementationError(subject, `No parent scope`);
83
+ }
84
+
85
+ owner = scope;
86
+ }
87
+
88
+ const useCache = options.forceCache || Object.isFrozen(owner);
89
+
90
+ let deconflictedMemberCache: Map<Model, Model[]> | undefined;
91
+ let conformantMemberCache: Map<Model, Model[]> | undefined;
92
+
93
+ let { featureNames, supportedFeatures } = owner as ClusterModel;
94
+ if (!featureNames) {
95
+ featureNames = new FeatureSet();
96
+ }
97
+ if (!supportedFeatures) {
98
+ supportedFeatures = new FeatureSet();
99
+ }
100
+
101
+ if (useCache && !options.disableCache) {
102
+ const cached = cache.get(owner);
103
+ if (cached) {
104
+ return cached;
105
+ }
106
+ deconflictedMemberCache = new Map();
107
+ conformantMemberCache = new Map();
108
+ }
109
+
110
+ let shadows: undefined | WeakMap<Model /* shadow */, Model[] /* canonical */>;
111
+ const canonicalIdentityLevels = {} as Record<string, { level: number; models: Model[] }>;
112
+ let level = 0;
113
+
114
+ new ModelTraversal().visitInheritance(owner, scope => {
115
+ level++;
116
+
117
+ for (const model of scope.children) {
118
+ for (const identity of [`n${model.tag}␜${model.name}␜${model.discriminator ?? ""}`]) {
119
+ const canonical = canonicalIdentityLevels[identity];
120
+ if (canonical === undefined) {
121
+ canonicalIdentityLevels[identity] = {
122
+ level,
123
+ models: [model],
124
+ };
125
+ } else if (canonical.level === level) {
126
+ canonical.models.push(model);
127
+ } else {
128
+ if (!shadows) {
129
+ shadows = new WeakMap();
130
+ }
131
+ shadows.set(model, canonical.models);
132
+ }
133
+ }
134
+ }
135
+ });
136
+
137
+ const result: Scope = {
138
+ owner,
139
+ isShadow: shadows ? model => shadows!.has(model as ValueModel) : () => false,
140
+ extensionOf: shadows
141
+ ? <T extends Model>(model?: T) => shadows!.get(model as unknown as ValueModel)?.[0] as T | undefined
142
+ : <T extends Model>(model?: T) => model,
143
+ modelFor: shadows
144
+ ? <T extends Model>(model: T) =>
145
+ (shadows!.get(model as unknown as ValueModel)?.[0] as T | undefined) ?? model
146
+ : <T extends Model>(model: T) => model,
147
+ membersOf,
148
+ };
149
+
150
+ function membersOf<T extends Model>(parent: T, options: Scope.MemberOptions = {}) {
151
+ const { conformance: conformanceMode, tags } = options;
152
+ const allMembers = findAllMembers(parent, Array.isArray(tags) ? new Set(tags) : (tags ?? DEFAULT_TAGS), result);
153
+
154
+ if (parent.tag === ElementTag.Cluster) {
155
+ injectGlobalAttributes(owner, allMembers);
156
+ }
157
+
158
+ if (!conformanceMode || conformanceMode === "ignore") {
159
+ return allMembers as Model.ChildOf<T>[];
160
+ }
161
+
162
+ const conformantOnly = conformanceMode === "conformant";
163
+
164
+ if (!conformantOnly && conformanceMode !== "deconflicted") {
165
+ throw new ImplementationError(`Invalid member conformance mode ${conformanceMode}`);
166
+ }
167
+
168
+ return filterWithConformance(
169
+ parent,
170
+ allMembers,
171
+ featureNames,
172
+ supportedFeatures,
173
+ conformantOnly,
174
+ conformantOnly ? deconflictedMemberCache : conformantMemberCache,
175
+ ) as Model.ChildOf<T>[];
176
+ }
177
+
178
+ if (useCache) {
179
+ cache.set(owner, result);
180
+ }
181
+
182
+ return result;
183
+ }
184
+
185
+ export namespace Scope {
186
+ /**
187
+ * Configuration for scope creation.
188
+ */
189
+ export interface ScopeOptions {
190
+ /**
191
+ * Force the result to cache regardless of whether model is frozen.
192
+ */
193
+ forceCache?: boolean;
194
+
195
+ /**
196
+ * Disable loading of model from cache. Will still write to cache if {@link forceCache} is true.
197
+ */
198
+ disableCache?: boolean;
199
+
200
+ /**
201
+ * Force the input model as an owner even if it is not a {@link ScopeOwner}.
202
+ */
203
+ forceOwner?: boolean;
204
+ }
205
+
206
+ /**
207
+ * Return all members regardless of conformance.
208
+ */
209
+ export const IgnoreConformance = "ignore";
210
+
211
+ /**
212
+ * Use conformance to resolve conflicts but otherwise return all members. Useful to detect errors in input
213
+ * that may contain non-conformant values.
214
+ */
215
+ export const DeconflictedConformance = "deconflicted";
216
+
217
+ /**
218
+ * Only return conformant members.
219
+ */
220
+ export const ConformantConformance = "conformant";
221
+
222
+ /**
223
+ * Determines how to apply conformance when selecting members.
224
+ */
225
+ export type ConformanceMode =
226
+ | typeof IgnoreConformance
227
+ | typeof DeconflictedConformance
228
+ | typeof ConformantConformance;
229
+
230
+ export interface MemberOptions {
231
+ /**
232
+ * Conformance filtering mode.
233
+ */
234
+ conformance?: ConformanceMode;
235
+
236
+ /**
237
+ * Applicable tags. Defaults to "field" and "attribute".
238
+ */
239
+ tags?: Set<ElementTag> | ElementTag[];
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Selects all candidate members for a model.
245
+ */
246
+ function findAllMembers(parent: Model, tags: Set<ElementTag>, scope: Scope) {
247
+ const members = Array<Model>();
248
+
249
+ // This is a map of identity (based on tag + id/name + discriminator) to a priority based on inheritance depth
250
+ const defined = {} as Record<string, number | undefined>;
251
+
252
+ const visited = new Set<Model>();
253
+
254
+ const traversal = new ModelTraversal();
255
+
256
+ let level = 0;
257
+ const childSearchVisitor = (model: Model) => {
258
+ // If the model has an extension that we haven't yet visited, we need to move search to the extension. This
259
+ // occurs if e.g. an attribute extends an attribute in a base cluster that references a datatype that is
260
+ // extended in the extended cluster
261
+ const extension = scope.modelFor(model);
262
+ if (extension !== model && !visited.has(extension)) {
263
+ traversal.visitInheritance(extension, childSearchVisitor);
264
+ return false;
265
+ }
266
+
267
+ visited.add(model);
268
+
269
+ level++;
270
+ for (const child of model.children) {
271
+ if (!tags.has(child.tag)) {
272
+ continue;
273
+ }
274
+
275
+ // Omit shadows
276
+ const nameIdentity = `s␜${child.tag}␜${child.name}␜${child.discriminator ?? ""}`;
277
+ const nameLevel = defined[nameIdentity];
278
+ if (nameLevel !== undefined && nameLevel < level) {
279
+ continue;
280
+ }
281
+
282
+ // Mark the level in which we saw these members
283
+ defined[nameIdentity] = level;
284
+
285
+ // Found a member
286
+ members.push(child);
287
+ }
288
+ };
289
+ traversal.visitInheritance(parent, childSearchVisitor);
290
+
291
+ return members;
292
+ }
293
+
294
+ /**
295
+ * We consider the standard set of "global" attributes members of all clusters. This injects those members that are
296
+ * not otherwise defined in the cluster.
297
+ */
298
+ function injectGlobalAttributes(scope: Model, members: Model[]) {
299
+ const missingGlobalIds = new Set(GLOBAL_IDS);
300
+ for (const m of members) {
301
+ if (m.tag === ElementTag.Attribute && m.id) {
302
+ missingGlobalIds.delete(m.id);
303
+ }
304
+ }
305
+
306
+ if (missingGlobalIds.size) {
307
+ const root = new ModelTraversal().findRoot(scope);
308
+ if (root) {
309
+ for (const id of missingGlobalIds) {
310
+ const global = root.children.select(id, [ElementTag.Attribute]);
311
+ if (global) {
312
+ members.push(global);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Filter selected members as follows:
321
+ *
322
+ * - If the member is deprecated, ignore it
323
+ *
324
+ * - If there is only a single member of a given name, select that member
325
+ *
326
+ * - If there are multiple members with the same name but there is no cluster, throw an error
327
+ *
328
+ * - If there are multiple members with the same name, use conformance to select the member that is applicable based
329
+ * on active features in the provided cluster
330
+ *
331
+ * - If there are multiple applicable members based on conformance the definitions conflict; and throw an error
332
+ *
333
+ * If the model is frozen we cache the return value.
334
+ *
335
+ * Note that "active" in this case does not imply the member is conformant, only that conflicts are resolved.
336
+ *
337
+ * Note 2 - members may not be differentiated with conformance rules that rely on field values in this way. That will
338
+ * probably never be necessary and would require an entirely different (more complicated) structure.
339
+ */
340
+ function filterWithConformance(
341
+ parent: Model,
342
+ members: Model[],
343
+ features: FeatureSet,
344
+ supportedFeatures: FeatureSet,
345
+ conformantOnly: boolean,
346
+ cache?: Map<Model, Model[]>,
347
+ ) {
348
+ const cached = cache?.get(parent);
349
+ if (cached) {
350
+ return cached;
351
+ }
352
+
353
+ const selectedMembers = {} as Record<string, Model>;
354
+ for (const member of members) {
355
+ const { conformance } = member as ValueModel;
356
+
357
+ if (!conformance) {
358
+ throw new ImplementationError(
359
+ `Conformance filtering invoked on ${member} which does not support conformance`,
360
+ );
361
+ }
362
+
363
+ if (conformantOnly && !conformance.isApplicable(features, supportedFeatures)) {
364
+ continue;
365
+ }
366
+
367
+ const other = selectedMembers[member.name];
368
+ if (other !== undefined) {
369
+ if (!conformantOnly && !conformance.isApplicable(features, supportedFeatures)) {
370
+ continue;
371
+ }
372
+
373
+ const { conformance: otherConformance } = other as ValueModel;
374
+ if (!otherConformance) {
375
+ throw new ImplementationError(
376
+ `Conformance filtering invoked on ${other} which does not support conformance`,
377
+ );
378
+ }
379
+
380
+ if (otherConformance.isApplicable(features, supportedFeatures)) {
381
+ throw new SchemaImplementationError(
382
+ parent,
383
+ `There are multiple definitions of "${member.name}" that cannot be differentiated by conformance`,
384
+ );
385
+ }
386
+
387
+ // This member takes precedence and will overwrite below
388
+ }
389
+
390
+ selectedMembers[member.name] = member;
391
+ }
392
+
393
+ const result = Object.values(selectedMembers);
394
+
395
+ if (cache) {
396
+ cache.set(parent, result);
397
+ }
398
+
399
+ return result;
400
+ }
@@ -13,5 +13,6 @@ export * from "./ClusterVariance.js";
13
13
  export * from "./DefaultValue.js";
14
14
  export * from "./MergedModel.js";
15
15
  export * from "./ModelVariantTraversal.js";
16
+ export * from "./Scope.js";
16
17
  export * from "./ValidateModel.js";
17
18
  import "./definition-validation/index.js";
@@ -36,6 +36,10 @@ export class AttributeModel extends PropertyModel<AttributeElement> implements A
36
36
  return this.effectiveQuality.changesOmitted;
37
37
  }
38
38
 
39
+ override get requiredFields() {
40
+ return { ...super.requiredFields, id: this.id };
41
+ }
42
+
39
43
  constructor(definition: AttributeElement.Properties) {
40
44
  super(definition);
41
45
  }
@@ -54,6 +54,11 @@ export interface Children<T extends Model = Model> extends Array<T> {
54
54
  */
55
55
  selectAll(selector: Children.Selector, allowedTags?: Children.TagSelector, except?: Set<Model>): Model.ChildOf<T>[];
56
56
 
57
+ /**
58
+ * Create a new child or patch existing children.
59
+ */
60
+ patchOrPush<C extends Model.Definition<T>>(child: C): void;
61
+
57
62
  /**
58
63
  * Models invoke this when their ID changes so we can update internal bookkeeping.
59
64
  */
@@ -245,7 +250,7 @@ export function Children<T extends Model = Model>(
245
250
  }
246
251
 
247
252
  /**
248
- * Add a child of the model. Adopts the mdodel and adds to any applicable indices.
253
+ * Add a child of the model. Adopts the model and adds to any applicable indices.
249
254
  */
250
255
  function addChild(child: Model) {
251
256
  if ((child.parent?.children as unknown) === children) {
@@ -428,6 +433,21 @@ export function Children<T extends Model = Model>(
428
433
  return results;
429
434
  }
430
435
 
436
+ function patchOrPush(child: Model.Definition<T>) {
437
+ validateChild(child);
438
+
439
+ const existing = selectAll.call(self, child.name, [child.tag as ElementTag]);
440
+ if (existing.length) {
441
+ // Patch
442
+ for (const toPatch of existing) {
443
+ toPatch.patch(child);
444
+ }
445
+ } else {
446
+ // Push
447
+ self.push(child);
448
+ }
449
+ }
450
+
431
451
  function updateId(child: Model, oldId: number | undefined) {
432
452
  if (!indices) {
433
453
  return;
@@ -541,6 +561,9 @@ export function Children<T extends Model = Model>(
541
561
  case "selectAll":
542
562
  return selectAll;
543
563
 
564
+ case "patchOrPush":
565
+ return patchOrPush;
566
+
544
567
  case "updateId":
545
568
  return updateId;
546
569
 
@@ -11,7 +11,6 @@ import { SchemaImplementationError } from "../common/errors.js";
11
11
  import { ElementTag, FeatureSet, Metatype } from "../common/index.js";
12
12
  import { Mei } from "../common/Mei.js";
13
13
  import { ClusterElement } from "../elements/index.js";
14
- import { ModelTraversal } from "../logic/ModelTraversal.js";
15
14
  import { ClusterRevision } from "../standard/elements/ClusterRevision.js";
16
15
  import { FeatureMap } from "../standard/elements/FeatureMap.js";
17
16
  import { Aspects } from "./Aspects.js";
@@ -22,16 +21,15 @@ import { DatatypeModel } from "./DatatypeModel.js";
22
21
  import { EventModel } from "./EventModel.js";
23
22
  import type { FieldModel } from "./FieldModel.js";
24
23
  import { Model } from "./Model.js";
25
- import { PropertyModel } from "./PropertyModel.js";
24
+ import { ScopeModel } from "./ScopeModel.js";
26
25
 
27
26
  const QUALITY = Symbol("quality");
28
27
 
29
- export class ClusterModel extends Model<ClusterElement> implements ClusterElement {
28
+ export class ClusterModel extends ScopeModel<ClusterElement> implements ClusterElement {
30
29
  override tag: ClusterElement.Tag = ClusterElement.Tag;
31
30
  declare id: Mei;
32
31
  declare classification?: ClusterElement.Classification;
33
32
  declare pics?: string;
34
- override isTypeScope = true;
35
33
 
36
34
  override get children(): Children<ClusterModel.Child> {
37
35
  return super.children as Children<ClusterModel.Child>;
@@ -71,53 +69,15 @@ export class ClusterModel extends Model<ClusterElement> implements ClusterElemen
71
69
  return this.all(DatatypeModel);
72
70
  }
73
71
 
74
- get members(): PropertyModel[] {
75
- const traversal = new ModelTraversal();
76
-
77
- // Formally a field element cannot be a cluster child but we allow it for metadata control when a field should
78
- // not be published
79
- const members = traversal.findChildren(this, [ElementTag.Field, ElementTag.Attribute]) as PropertyModel[];
80
-
81
- // We consider the standard set of "global" attributes members of all clusters
82
- const missingGlobalIds = new Set(AttributeModel.globalIds);
83
- for (const m of members) {
84
- if (m instanceof AttributeModel && m.id) {
85
- missingGlobalIds.delete(m.id);
86
- }
87
- }
88
-
89
- if (missingGlobalIds.size) {
90
- const root = traversal.findRoot(this);
91
- if (root) {
92
- for (const id of missingGlobalIds) {
93
- const global = root.get(AttributeModel, id);
94
- if (global) {
95
- members.push(global);
96
- }
97
- }
98
- }
99
- }
100
-
101
- return members;
102
- }
103
-
104
- get activeMembers() {
105
- return new ModelTraversal().findActiveMembers(this, false, this);
106
- }
107
-
108
- get conformantMembers() {
109
- return new ModelTraversal().findActiveMembers(this, true, this);
110
- }
111
-
112
72
  /**
113
73
  * Get attributes, commands and events whether inherited or defined directly in this model.
114
74
  */
115
75
  get allAces() {
116
- return new ModelTraversal().findChildren(this, [
117
- ElementTag.Attribute,
118
- ElementTag.Command,
119
- ElementTag.Event,
120
- ]) as (AttributeModel | CommandModel | EventModel)[];
76
+ return this.scope.membersOf(this, { tags: [ElementTag.Attribute, ElementTag.Command, ElementTag.Event] }) as (
77
+ | AttributeModel
78
+ | CommandModel
79
+ | EventModel
80
+ )[];
121
81
  }
122
82
 
123
83
  get revision() {
@@ -202,8 +162,8 @@ export class ClusterModel extends Model<ClusterElement> implements ClusterElemen
202
162
  return result as ClusterElement;
203
163
  }
204
164
 
205
- constructor(definition: ClusterElement.Properties) {
206
- super(definition);
165
+ constructor(definition: ClusterModel.Definition, ...children: Model.Definition<ClusterModel.Child>[]) {
166
+ super(definition, ...children);
207
167
 
208
168
  if (definition instanceof Model) {
209
169
  Aspects.cloneAspects(definition, this, QUALITY);
@@ -218,13 +178,14 @@ export class ClusterModel extends Model<ClusterElement> implements ClusterElemen
218
178
  }
219
179
 
220
180
  export namespace ClusterModel {
181
+ export type Definition = ClusterElement.Properties & { supportedFeatures?: FeatureSet.Definition };
182
+
221
183
  export type Child =
222
184
  | DatatypeModel
223
185
  | AttributeModel
224
186
  | CommandModel
225
187
  | EventModel
226
188
 
227
- // Fields are not cluster children in canonical schema but we allow
228
- // them as private values in operational schema
189
+ // Fields are not cluster children in canonical schema but we allow them as private values in operational schema
229
190
  | FieldModel;
230
191
  }
@@ -36,6 +36,10 @@ export class CommandModel extends ValueModel<CommandElement> implements CommandE
36
36
  return this.direction ?? (new ModelTraversal().findShadow(this) as CommandModel | undefined)?.direction;
37
37
  }
38
38
 
39
+ override get requiredFields() {
40
+ return { ...super.requiredFields, id: this.id };
41
+ }
42
+
39
43
  /**
40
44
  * Commands may re-use the ID for request and response so identification requires the ID in conjunction with the
41
45
  * direction.
@@ -27,5 +27,3 @@ export class DatatypeModel extends ValueModel<DatatypeElement> implements Dataty
27
27
  Model.types[DatatypeElement.Tag] = this;
28
28
  }
29
29
  }
30
- const x = {} as DatatypeModel;
31
- x.children;
@@ -22,6 +22,10 @@ export class EventModel extends ValueModel<EventElement> implements EventElement
22
22
  return this.effectiveAccess.fabricSensitive;
23
23
  }
24
24
 
25
+ override get requiredFields() {
26
+ return { ...super.requiredFields, id: this.id };
27
+ }
28
+
25
29
  static {
26
30
  Model.types[EventElement.Tag] = this;
27
31
  }
@@ -16,14 +16,14 @@ import { FabricModel } from "./FabricModel.js";
16
16
  import { FieldModel } from "./FieldModel.js";
17
17
  import { Globals } from "./Globals.js";
18
18
  import { Model } from "./Model.js";
19
+ import { ScopeModel } from "./ScopeModel.js";
19
20
  import { SemanticNamespaceModel } from "./SemanticNamespaceModel.js";
20
21
 
21
22
  /**
22
23
  * The root of a Matter model. This is the parent for global models.
23
24
  */
24
- export class MatterModel extends Model<MatterElement> implements MatterElement {
25
+ export class MatterModel extends ScopeModel<MatterElement> implements MatterElement {
25
26
  override tag: MatterElement.Tag = MatterElement.Tag;
26
- override isTypeScope = true;
27
27
  declare revision?: Specification.Revision;
28
28
 
29
29
  override get children(): Children<MatterModel.Child> {
@@ -89,9 +89,10 @@ export class MatterModel extends Model<MatterElement> implements MatterElement {
89
89
  *
90
90
  * @param definition the MatterElement that defines the model
91
91
  */
92
- constructor(definition: MatterElement.Properties = { name: "Matter", children: [] }) {
93
- const children = [...(definition.children || [])];
94
- super({ ...definition, name: definition.name, children });
92
+ constructor(definition: MatterElement.Definition, ...children: Model.Definition<MatterModel.Child>[]) {
93
+ const name = definition.name ?? "Matter";
94
+ const definitionChildren = [...(definition.children || [])];
95
+ super({ ...definition, name, children: definitionChildren }, ...children);
95
96
  }
96
97
 
97
98
  /**