@matter/node 0.17.3 → 0.17.4-alpha.0-20260623-5647634fc
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/behavior/cluster/FabricScopedDataHandler.d.ts +10 -2
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.d.ts.map +1 -1
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.js +31 -23
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.js.map +1 -1
- package/dist/cjs/behavior/state/managed/Datasource.d.ts +9 -0
- package/dist/cjs/behavior/state/managed/Datasource.d.ts.map +1 -1
- package/dist/cjs/behavior/state/managed/Datasource.js +3 -0
- package/dist/cjs/behavior/state/managed/Datasource.js.map +1 -1
- package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
- package/dist/cjs/node/client/ClientStructure.js +3 -3
- package/dist/cjs/node/client/ClientStructure.js.map +1 -1
- package/dist/cjs/node/server/ServerEndpointInitializer.js +1 -1
- package/dist/cjs/node/server/ServerEndpointInitializer.js.map +1 -1
- package/dist/cjs/storage/client/DatasourceCache.d.ts +5 -0
- package/dist/cjs/storage/client/DatasourceCache.d.ts.map +1 -1
- package/dist/cjs/storage/client/DatasourceCache.js +9 -2
- package/dist/cjs/storage/client/DatasourceCache.js.map +1 -1
- package/dist/cjs/storage/server/ServerNodeStore.d.ts.map +1 -1
- package/dist/cjs/storage/server/ServerNodeStore.js +3 -1
- package/dist/cjs/storage/server/ServerNodeStore.js.map +1 -1
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.d.ts +10 -2
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.d.ts.map +1 -1
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.js +31 -23
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.js.map +1 -1
- package/dist/esm/behavior/state/managed/Datasource.d.ts +9 -0
- package/dist/esm/behavior/state/managed/Datasource.d.ts.map +1 -1
- package/dist/esm/behavior/state/managed/Datasource.js +3 -0
- package/dist/esm/behavior/state/managed/Datasource.js.map +1 -1
- package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
- package/dist/esm/node/client/ClientStructure.js +3 -3
- package/dist/esm/node/client/ClientStructure.js.map +1 -1
- package/dist/esm/node/server/ServerEndpointInitializer.js +1 -1
- package/dist/esm/node/server/ServerEndpointInitializer.js.map +1 -1
- package/dist/esm/storage/client/DatasourceCache.d.ts +5 -0
- package/dist/esm/storage/client/DatasourceCache.d.ts.map +1 -1
- package/dist/esm/storage/client/DatasourceCache.js +9 -2
- package/dist/esm/storage/client/DatasourceCache.js.map +1 -1
- package/dist/esm/storage/server/ServerNodeStore.d.ts.map +1 -1
- package/dist/esm/storage/server/ServerNodeStore.js +3 -1
- package/dist/esm/storage/server/ServerNodeStore.js.map +1 -1
- package/package.json +6 -6
- package/src/behavior/cluster/FabricScopedDataHandler.ts +58 -34
- package/src/behavior/state/managed/Datasource.ts +15 -0
- package/src/node/client/ClientStructure.ts +3 -4
- package/src/node/server/ServerEndpointInitializer.ts +1 -1
- package/src/storage/client/DatasourceCache.ts +12 -2
- package/src/storage/server/ServerNodeStore.ts +2 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Behavior } from "#behavior/Behavior.js";
|
|
8
|
+
import type { Agent } from "#endpoint/Agent.js";
|
|
8
9
|
import type { Endpoint } from "#endpoint/Endpoint.js";
|
|
9
10
|
import type { SupportedElements } from "#endpoint/properties/Behaviors.js";
|
|
10
11
|
import type { ServerNode } from "#node/ServerNode.js";
|
|
@@ -34,18 +35,15 @@ async function forBehaviors(
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
38
|
+
* Compute a state patch that strips fabric-scoped attribute entries no longer belonging to an allowed fabric.
|
|
39
|
+
* Returns `undefined` when nothing needs to change.
|
|
39
40
|
*/
|
|
40
|
-
|
|
41
|
+
function computeFabricScopedUpdate(
|
|
41
42
|
endpoint: Endpoint,
|
|
42
43
|
type: Behavior.Type,
|
|
43
|
-
cluster: ClusterType,
|
|
44
44
|
supportedAttributes: Set<string>,
|
|
45
45
|
allowedIndices: FabricIndex[],
|
|
46
|
-
) {
|
|
47
|
-
const stateUpdatePromises = new Array<Promise<void>>();
|
|
48
|
-
|
|
46
|
+
): Val.Struct | undefined {
|
|
49
47
|
// Build a set of fabric-scoped attribute names from the schema
|
|
50
48
|
const fabricScopedAttrs = new Set<string>();
|
|
51
49
|
const schema = Schema(type) as ClusterModel;
|
|
@@ -58,11 +56,11 @@ async function sanitizeAttributeData(
|
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
const stateUpdate = {} as Val.Struct;
|
|
61
|
-
|
|
59
|
+
const state = endpoint.stateOf(type) as Val.Struct;
|
|
62
60
|
for (const attributeName of supportedAttributes) {
|
|
63
61
|
if (fabricScopedAttrs.has(attributeName)) {
|
|
64
|
-
const value =
|
|
65
|
-
// If the value contains data for
|
|
62
|
+
const value = state[attributeName];
|
|
63
|
+
// If the value contains data for a fabric no longer present, remove that data
|
|
66
64
|
if (Array.isArray(value) && value.length > 0) {
|
|
67
65
|
const filtered = value.filter(entry => allowedIndices.includes(entry.fabricIndex));
|
|
68
66
|
if (filtered.length !== value.length) {
|
|
@@ -71,20 +69,38 @@ async function sanitizeAttributeData(
|
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
return Object.keys(stateUpdate).length > 0 ? stateUpdate : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sanitize fabric-scoped attributes for an already-active endpoint via {@link Endpoint.setStateOf}.
|
|
77
|
+
* The changed state is returned in an array of promises and should be awaited to ensure the state is updated.
|
|
78
|
+
*/
|
|
79
|
+
async function sanitizeAttributeData(
|
|
80
|
+
endpoint: Endpoint,
|
|
81
|
+
type: Behavior.Type,
|
|
82
|
+
cluster: ClusterType,
|
|
83
|
+
supportedAttributes: Set<string>,
|
|
84
|
+
allowedIndices: FabricIndex[],
|
|
85
|
+
) {
|
|
86
|
+
const stateUpdate = computeFabricScopedUpdate(endpoint, type, supportedAttributes, allowedIndices);
|
|
87
|
+
if (stateUpdate === undefined) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stateUpdatePromises = new Array<Promise<void>>();
|
|
92
|
+
// Best-effort during online fabric removal: a per-cluster failure must not abort the removal already in progress.
|
|
93
|
+
// The startup path (limitEndpointAttributeDataToAllowedFabrics) instead lets failures throw.
|
|
94
|
+
const { resolver, promise } = createPromise<void>();
|
|
95
|
+
(endpoint.eventsOf(type) as Behavior.EventsOf<any>).stateChanged?.on(resolver);
|
|
96
|
+
try {
|
|
97
|
+
await endpoint.setStateOf(type, stateUpdate);
|
|
98
|
+
stateUpdatePromises.push(withTimeout(Seconds(5), promise)); // 5s should be enough for state change
|
|
99
|
+
} catch (error) {
|
|
100
|
+
logger.warn(
|
|
101
|
+
`Could not sanitize fabric-scoped attributes for cluster ${cluster.name} on endpoint ${endpoint.id}`,
|
|
102
|
+
error,
|
|
103
|
+
);
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
return stateUpdatePromises;
|
|
@@ -151,16 +167,24 @@ export async function limitNodeDataToAllowedFabrics(node: ServerNode, allowedInd
|
|
|
151
167
|
}
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Sanitize fabric-scoped attributes for a single endpoint while it is still initializing.
|
|
172
|
+
*
|
|
173
|
+
* Invoked from {@link EndpointInitializer.behaviorsInitialized}, which runs inside the endpoint's construction
|
|
174
|
+
* context. The endpoint is not yet active, so {@link Endpoint.setStateOf} (which opens a fresh action that asserts an
|
|
175
|
+
* active endpoint) would throw. Instead we patch state through the supplied {@link Agent}, whose context is the
|
|
176
|
+
* construction transaction that commits when initialization completes.
|
|
177
|
+
*/
|
|
178
|
+
export async function limitEndpointAttributeDataToAllowedFabrics(agent: Agent, allowedIndices: FabricIndex[]) {
|
|
179
|
+
await forBehaviors(agent.endpoint, async (type, _cluster, elements) => {
|
|
180
|
+
if (!elements.attributes.size) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const stateUpdate = computeFabricScopedUpdate(agent.endpoint, type, elements.attributes, allowedIndices);
|
|
184
|
+
if (stateUpdate === undefined) {
|
|
185
|
+
return;
|
|
161
186
|
}
|
|
187
|
+
const behavior = agent.get(type);
|
|
188
|
+
behavior.type.supervisor.patch(stateUpdate, behavior.state, agent.endpoint.path);
|
|
162
189
|
});
|
|
163
|
-
|
|
164
|
-
// Wait for all state changed to be executed before processing events
|
|
165
|
-
await Promise.allSettled(stateUpdatePromises);
|
|
166
190
|
}
|
|
@@ -192,6 +192,12 @@ export namespace Datasource {
|
|
|
192
192
|
*/
|
|
193
193
|
consumer?: ExternallyMutableStore.Consumer;
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Current values, preferring the live consumer over {@link Store.initialValues}. Reflects up-to-date
|
|
197
|
+
* data while a consumer is attached, where {@link Store.initialValues} may be absent.
|
|
198
|
+
*/
|
|
199
|
+
currentValues?: Val.Struct;
|
|
200
|
+
|
|
195
201
|
/**
|
|
196
202
|
* The current version of the data.
|
|
197
203
|
*/
|
|
@@ -213,6 +219,11 @@ export namespace Datasource {
|
|
|
213
219
|
*/
|
|
214
220
|
readValues(keys: Set<string>): Val.Struct;
|
|
215
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Read a non-destructive copy of all current values.
|
|
224
|
+
*/
|
|
225
|
+
snapshot(): Val.Struct;
|
|
226
|
+
|
|
216
227
|
/**
|
|
217
228
|
* Release all values from the datasource, transferring ownership back to the store.
|
|
218
229
|
*/
|
|
@@ -557,6 +568,10 @@ class DatasourceImpl implements Datasource, Datasource.ExternallyMutableStore.Co
|
|
|
557
568
|
return result;
|
|
558
569
|
}
|
|
559
570
|
|
|
571
|
+
snapshot() {
|
|
572
|
+
return { ...this.#values };
|
|
573
|
+
}
|
|
574
|
+
|
|
560
575
|
releaseValues() {
|
|
561
576
|
const { values } = this;
|
|
562
577
|
this.values = {};
|
|
@@ -501,9 +501,8 @@ export class ClientStructure {
|
|
|
501
501
|
|
|
502
502
|
// Generate a behavior if enough information is available
|
|
503
503
|
if (cluster.behavior === undefined) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
504
|
+
const values = cluster.store.currentValues;
|
|
505
|
+
if (values) {
|
|
507
506
|
const clusterRevision = getStoreValue(values, ClusterRevision.id, "clusterRevision");
|
|
508
507
|
const features = getStoreValue(values, FeatureMap.id, "featureMap");
|
|
509
508
|
const attributeList = getStoreValue(values, AttributeList.id, "attributeList");
|
|
@@ -579,7 +578,7 @@ export class ClientStructure {
|
|
|
579
578
|
if (cluster.behavior && endpoint.behaviors.isActive(cluster.behavior.id)) {
|
|
580
579
|
attrs = endpoint.stateOf(cluster.behavior);
|
|
581
580
|
} else {
|
|
582
|
-
attrs = cluster.store.
|
|
581
|
+
attrs = cluster.store.currentValues ?? {};
|
|
583
582
|
}
|
|
584
583
|
this.#synchronizeDescriptor(structure, attrs);
|
|
585
584
|
}
|
|
@@ -116,7 +116,7 @@ export class ServerEndpointInitializer extends EndpointInitializer {
|
|
|
116
116
|
if (agent.env.has(FabricManager)) {
|
|
117
117
|
const fabricIndices = agent.env.get(FabricManager).fabrics.map(fabric => fabric.fabricIndex);
|
|
118
118
|
if (fabricIndices.length > 0) {
|
|
119
|
-
return limitEndpointAttributeDataToAllowedFabrics(agent
|
|
119
|
+
return limitEndpointAttributeDataToAllowedFabrics(agent, fabricIndices);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -52,9 +52,20 @@ export class DatasourceCache implements Datasource.ExternallyMutableStore, Remot
|
|
|
52
52
|
this.#consumer = consumer;
|
|
53
53
|
if (consumer !== undefined) {
|
|
54
54
|
this.#reclaimed = false;
|
|
55
|
+
|
|
56
|
+
// Consumer owns the values while attached; drop our seed copy so we don't shadow it.
|
|
57
|
+
this.initialValues = undefined;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Current values, preferring the live consumer over the seed snapshot, so cluster structure can rebuild from
|
|
63
|
+
* up-to-date data while a behavior is active.
|
|
64
|
+
*/
|
|
65
|
+
get currentValues(): Val.Struct | undefined {
|
|
66
|
+
return this.#consumer && !this.#reclaimed ? this.#consumer.snapshot() : this.initialValues;
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
async set(transaction: Transaction, values: Val.Struct) {
|
|
59
70
|
let participant = transaction.getParticipant(this.#writer);
|
|
60
71
|
if (participant === undefined) {
|
|
@@ -126,8 +137,7 @@ export class DatasourceCache implements Datasource.ExternallyMutableStore, Remot
|
|
|
126
137
|
if (!this.initialValues) {
|
|
127
138
|
this.initialValues = {};
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
Object.assign(this.initialValues, valuesStruct);
|
|
140
|
+
Object.assign(this.initialValues, Object.fromEntries(values));
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
|
|
@@ -105,6 +105,8 @@ export class ServerNodeStore extends NodeStore implements Destructable {
|
|
|
105
105
|
Diagnostic.dict({
|
|
106
106
|
location: root?.path ?? "(unknown location)",
|
|
107
107
|
driver: this.#storageManager?.driverId ?? "unknown",
|
|
108
|
+
// Only meaningful on close, where a non-zero count means a holder did not release
|
|
109
|
+
...(what === "Closed" && root ? { locks: root.lockCount } : {}),
|
|
108
110
|
}),
|
|
109
111
|
);
|
|
110
112
|
}
|