@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.
- package/dist/cjs/elements/MatterElement.d.ts +5 -1
- package/dist/cjs/elements/MatterElement.d.ts.map +1 -1
- package/dist/cjs/elements/MatterElement.js +1 -1
- package/dist/cjs/elements/MatterElement.js.map +1 -1
- package/dist/cjs/logic/DefaultValue.d.ts +3 -1
- package/dist/cjs/logic/DefaultValue.d.ts.map +1 -1
- package/dist/cjs/logic/DefaultValue.js +11 -11
- package/dist/cjs/logic/DefaultValue.js.map +1 -1
- package/dist/cjs/logic/ModelTraversal.d.ts +6 -35
- package/dist/cjs/logic/ModelTraversal.d.ts.map +1 -1
- package/dist/cjs/logic/ModelTraversal.js +15 -109
- package/dist/cjs/logic/ModelTraversal.js.map +1 -1
- package/dist/cjs/logic/ModelVariantTraversal.d.ts.map +1 -1
- package/dist/cjs/logic/ModelVariantTraversal.js.map +1 -1
- package/dist/cjs/logic/Scope.d.ts +102 -0
- package/dist/cjs/logic/Scope.d.ts.map +1 -0
- package/dist/cjs/logic/Scope.js +220 -0
- package/dist/cjs/logic/Scope.js.map +6 -0
- package/dist/cjs/logic/definition-validation/ModelValidator.d.ts.map +1 -1
- package/dist/cjs/logic/index.d.ts +1 -0
- package/dist/cjs/logic/index.d.ts.map +1 -1
- package/dist/cjs/logic/index.js +1 -0
- package/dist/cjs/logic/index.js.map +1 -1
- package/dist/cjs/models/AttributeModel.d.ts +20 -0
- package/dist/cjs/models/AttributeModel.d.ts.map +1 -1
- package/dist/cjs/models/AttributeModel.js +3 -0
- package/dist/cjs/models/AttributeModel.js.map +1 -1
- package/dist/cjs/models/Children.d.ts +4 -0
- package/dist/cjs/models/Children.d.ts.map +1 -1
- package/dist/cjs/models/Children.js +13 -0
- package/dist/cjs/models/Children.js.map +1 -1
- package/dist/cjs/models/ClusterModel.d.ts +6 -7
- package/dist/cjs/models/ClusterModel.d.ts.map +1 -1
- package/dist/cjs/models/ClusterModel.js +5 -38
- package/dist/cjs/models/ClusterModel.js.map +1 -1
- package/dist/cjs/models/CommandModel.d.ts +22 -0
- package/dist/cjs/models/CommandModel.d.ts.map +1 -1
- package/dist/cjs/models/CommandModel.js +3 -0
- package/dist/cjs/models/CommandModel.js.map +1 -1
- package/dist/cjs/models/DatatypeModel.js +0 -2
- package/dist/cjs/models/DatatypeModel.js.map +1 -1
- package/dist/cjs/models/EventModel.d.ts +21 -0
- package/dist/cjs/models/EventModel.d.ts.map +1 -1
- package/dist/cjs/models/EventModel.js +3 -0
- package/dist/cjs/models/EventModel.js.map +1 -1
- package/dist/cjs/models/MatterModel.d.ts +3 -3
- package/dist/cjs/models/MatterModel.d.ts.map +1 -1
- package/dist/cjs/models/MatterModel.js +6 -5
- package/dist/cjs/models/MatterModel.js.map +1 -1
- package/dist/cjs/models/Model.d.ts +10 -6
- package/dist/cjs/models/Model.d.ts.map +1 -1
- package/dist/cjs/models/Model.js +28 -9
- package/dist/cjs/models/Model.js.map +1 -1
- package/dist/cjs/models/ScopeModel.d.ts +22 -0
- package/dist/cjs/models/ScopeModel.d.ts.map +1 -0
- package/dist/cjs/models/ScopeModel.js +63 -0
- package/dist/cjs/models/ScopeModel.js.map +6 -0
- package/dist/cjs/models/ValueModel.d.ts +7 -21
- package/dist/cjs/models/ValueModel.d.ts.map +1 -1
- package/dist/cjs/models/ValueModel.js +15 -34
- package/dist/cjs/models/ValueModel.js.map +1 -1
- package/dist/cjs/models/index.d.ts +1 -0
- package/dist/cjs/models/index.d.ts.map +1 -1
- package/dist/cjs/models/index.js +1 -0
- package/dist/cjs/models/index.js.map +1 -1
- package/dist/cjs/standard/elements/ColorControl.js +3 -4
- package/dist/cjs/standard/elements/ColorControl.js.map +1 -1
- package/dist/cjs/standard/elements/ModeBase.js +1 -1
- package/dist/cjs/standard/elements/ModeBase.js.map +1 -1
- package/dist/cjs/standard/elements/PumpConfigurationAndControl.d.ts.map +1 -1
- package/dist/cjs/standard/elements/PumpConfigurationAndControl.js +3 -20
- package/dist/cjs/standard/elements/PumpConfigurationAndControl.js.map +1 -1
- package/dist/cjs/standard/elements/UserLabel.d.ts.map +1 -1
- package/dist/cjs/standard/elements/UserLabel.js +13 -16
- package/dist/cjs/standard/elements/UserLabel.js.map +1 -1
- package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.d.ts.map +1 -1
- package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.js +1 -5
- package/dist/cjs/standard/elements/WildcardPathFlagsBitmap.js.map +1 -1
- package/dist/cjs/standard/elements/WindowCovering.js +2 -2
- package/dist/esm/elements/MatterElement.d.ts +5 -1
- package/dist/esm/elements/MatterElement.d.ts.map +1 -1
- package/dist/esm/elements/MatterElement.js +1 -1
- package/dist/esm/elements/MatterElement.js.map +1 -1
- package/dist/esm/logic/DefaultValue.d.ts +3 -1
- package/dist/esm/logic/DefaultValue.d.ts.map +1 -1
- package/dist/esm/logic/DefaultValue.js +11 -11
- package/dist/esm/logic/DefaultValue.js.map +1 -1
- package/dist/esm/logic/ModelTraversal.d.ts +6 -35
- package/dist/esm/logic/ModelTraversal.d.ts.map +1 -1
- package/dist/esm/logic/ModelTraversal.js +16 -110
- package/dist/esm/logic/ModelTraversal.js.map +1 -1
- package/dist/esm/logic/ModelVariantTraversal.d.ts.map +1 -1
- package/dist/esm/logic/ModelVariantTraversal.js.map +1 -1
- package/dist/esm/logic/Scope.d.ts +102 -0
- package/dist/esm/logic/Scope.d.ts.map +1 -0
- package/dist/esm/logic/Scope.js +200 -0
- package/dist/esm/logic/Scope.js.map +6 -0
- package/dist/esm/logic/definition-validation/ModelValidator.d.ts.map +1 -1
- package/dist/esm/logic/index.d.ts +1 -0
- package/dist/esm/logic/index.d.ts.map +1 -1
- package/dist/esm/logic/index.js +1 -0
- package/dist/esm/logic/index.js.map +1 -1
- package/dist/esm/models/AttributeModel.d.ts +20 -0
- package/dist/esm/models/AttributeModel.d.ts.map +1 -1
- package/dist/esm/models/AttributeModel.js +3 -0
- package/dist/esm/models/AttributeModel.js.map +1 -1
- package/dist/esm/models/Children.d.ts +4 -0
- package/dist/esm/models/Children.d.ts.map +1 -1
- package/dist/esm/models/Children.js +13 -0
- package/dist/esm/models/Children.js.map +1 -1
- package/dist/esm/models/ClusterModel.d.ts +6 -7
- package/dist/esm/models/ClusterModel.d.ts.map +1 -1
- package/dist/esm/models/ClusterModel.js +5 -38
- package/dist/esm/models/ClusterModel.js.map +1 -1
- package/dist/esm/models/CommandModel.d.ts +22 -0
- package/dist/esm/models/CommandModel.d.ts.map +1 -1
- package/dist/esm/models/CommandModel.js +3 -0
- package/dist/esm/models/CommandModel.js.map +1 -1
- package/dist/esm/models/DatatypeModel.js +0 -2
- package/dist/esm/models/DatatypeModel.js.map +1 -1
- package/dist/esm/models/EventModel.d.ts +21 -0
- package/dist/esm/models/EventModel.d.ts.map +1 -1
- package/dist/esm/models/EventModel.js +3 -0
- package/dist/esm/models/EventModel.js.map +1 -1
- package/dist/esm/models/MatterModel.d.ts +3 -3
- package/dist/esm/models/MatterModel.d.ts.map +1 -1
- package/dist/esm/models/MatterModel.js +6 -5
- package/dist/esm/models/MatterModel.js.map +1 -1
- package/dist/esm/models/Model.d.ts +10 -6
- package/dist/esm/models/Model.d.ts.map +1 -1
- package/dist/esm/models/Model.js +28 -9
- package/dist/esm/models/Model.js.map +1 -1
- package/dist/esm/models/ScopeModel.d.ts +22 -0
- package/dist/esm/models/ScopeModel.d.ts.map +1 -0
- package/dist/esm/models/ScopeModel.js +43 -0
- package/dist/esm/models/ScopeModel.js.map +6 -0
- package/dist/esm/models/ValueModel.d.ts +7 -21
- package/dist/esm/models/ValueModel.d.ts.map +1 -1
- package/dist/esm/models/ValueModel.js +15 -34
- package/dist/esm/models/ValueModel.js.map +1 -1
- package/dist/esm/models/index.d.ts +1 -0
- package/dist/esm/models/index.d.ts.map +1 -1
- package/dist/esm/models/index.js +1 -0
- package/dist/esm/models/index.js.map +1 -1
- package/dist/esm/standard/elements/ColorControl.js +3 -4
- package/dist/esm/standard/elements/ColorControl.js.map +1 -1
- package/dist/esm/standard/elements/ModeBase.js +1 -1
- package/dist/esm/standard/elements/ModeBase.js.map +1 -1
- package/dist/esm/standard/elements/PumpConfigurationAndControl.d.ts.map +1 -1
- package/dist/esm/standard/elements/PumpConfigurationAndControl.js +3 -20
- package/dist/esm/standard/elements/PumpConfigurationAndControl.js.map +1 -1
- package/dist/esm/standard/elements/UserLabel.d.ts.map +1 -1
- package/dist/esm/standard/elements/UserLabel.js +13 -16
- package/dist/esm/standard/elements/UserLabel.js.map +1 -1
- package/dist/esm/standard/elements/WildcardPathFlagsBitmap.d.ts.map +1 -1
- package/dist/esm/standard/elements/WildcardPathFlagsBitmap.js +1 -5
- package/dist/esm/standard/elements/WildcardPathFlagsBitmap.js.map +1 -1
- package/dist/esm/standard/elements/WindowCovering.js +2 -2
- package/package.json +4 -4
- package/src/elements/MatterElement.ts +7 -2
- package/src/logic/DefaultValue.ts +13 -11
- package/src/logic/ModelTraversal.ts +28 -147
- package/src/logic/ModelVariantTraversal.ts +0 -1
- package/src/logic/Scope.ts +400 -0
- package/src/logic/index.ts +1 -0
- package/src/models/AttributeModel.ts +4 -0
- package/src/models/Children.ts +24 -1
- package/src/models/ClusterModel.ts +12 -51
- package/src/models/CommandModel.ts +4 -0
- package/src/models/DatatypeModel.ts +0 -2
- package/src/models/EventModel.ts +4 -0
- package/src/models/MatterModel.ts +6 -5
- package/src/models/Model.ts +38 -12
- package/src/models/ScopeModel.ts +55 -0
- package/src/models/ValueModel.ts +17 -39
- package/src/models/index.ts +1 -0
- package/src/standard/elements/ColorControl.ts +4 -4
- package/src/standard/elements/ModeBase.ts +1 -1
- package/src/standard/elements/PumpConfigurationAndControl.ts +2 -18
- package/src/standard/elements/UserLabel.ts +8 -12
- package/src/standard/elements/WildcardPathFlagsBitmap.ts +1 -4
- 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
|
+
}
|
package/src/logic/index.ts
CHANGED
|
@@ -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
|
}
|
package/src/models/Children.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
24
|
+
import { ScopeModel } from "./ScopeModel.js";
|
|
26
25
|
|
|
27
26
|
const QUALITY = Symbol("quality");
|
|
28
27
|
|
|
29
|
-
export class ClusterModel extends
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
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.
|
package/src/models/EventModel.ts
CHANGED
|
@@ -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
|
|
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.
|
|
93
|
-
const
|
|
94
|
-
|
|
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
|
/**
|