UNPKG

@project-chip/matter.js

Version:
197 lines (173 loc) 7.23 kB
/** * @license * Copyright 2022-2025 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import type { StorageContext } from "#general"; import { Construction, Logger } from "#general"; import { DecodedAttributeReportValue, PeerDataStore, Val } from "#protocol"; import { AttributeId, ClusterId, EndpointNumber, EventNumber } from "#types"; import { ClientEndpointStore } from "./ClientEndpointStore.js"; const logger = Logger.get("CachedClientNodeStore"); const VERSION_KEY = "__version__"; const MAX_EVENT_NUMBER_KEY = "__maxEventNumber__"; type PersistedAttributeValue = { value: Val; attributeName: string; }; export class CachedClientNodeStore extends PeerDataStore { #storage: StorageContext; #construction: Construction<CachedClientNodeStore>; #endpointStores = new Map<EndpointNumber, ClientEndpointStore>(); #maxEventNumber: EventNumber = EventNumber(0); get construction() { return this.#construction; } constructor(storage: StorageContext, load = true) { super(); this.#storage = storage; this.#construction = Construction(this, async () => { if (!load) { return; } this.#maxEventNumber = await this.#storage.get(MAX_EVENT_NUMBER_KEY, EventNumber(0)); // Preload stores so we can access synchronously going forward const knownParts = await this.#storage.contexts(); for (const partId of knownParts) { await this.#loadEndpointStore(partId); } }); } async #loadEndpointStore(partId: string) { const endpointNumber = EndpointNumber(parseInt(partId)); const endpointStore = new ClientEndpointStore(this.#storage.createContext(partId)); this.#endpointStores.set(endpointNumber, endpointStore); await endpointStore.construction; } #storeForEndpoint(endpointId: EndpointNumber): ClientEndpointStore { this.#construction.assert(); const existingStore = this.#endpointStores.get(endpointId); if (existingStore !== undefined) { return existingStore; } const store = new ClientEndpointStore(this.#storage.createContext(endpointId.toString()), false); this.#endpointStores.set(endpointId, store); return store; } get maxEventNumber() { return this.#maxEventNumber; } updateLastEventNumber(eventNumber: EventNumber) { this.#maxEventNumber = eventNumber; return this.#storage.set(MAX_EVENT_NUMBER_KEY, eventNumber); } retrieveAttribute( endpointId: EndpointNumber, clusterId: ClusterId, attributeId: AttributeId, ): DecodedAttributeReportValue<any> | undefined { const store = this.#storeForEndpoint(endpointId); const clusterValues = store.get.get(clusterId.toString()); if (clusterValues === undefined) { return undefined; } const version = clusterValues[VERSION_KEY]; if (version === undefined || typeof version !== "number") { return undefined; } const attributeIdStr = attributeId?.toString(); const data = clusterValues[attributeIdStr] as PersistedAttributeValue; const { value, attributeName } = data ?? {}; if (value === undefined) { return undefined; } return { path: { endpointId, clusterId, attributeId, attributeName, }, value, version, }; } retrieveAttributes(endpointId: EndpointNumber, clusterId: ClusterId): DecodedAttributeReportValue<any>[] { const store = this.#storeForEndpoint(endpointId); const clusterValues = store.get.get(clusterId.toString()); if (clusterValues === undefined) { return []; } const version = clusterValues[VERSION_KEY]; if (version === undefined || typeof version !== "number") { return []; } return Object.keys(clusterValues) .filter(key => key !== VERSION_KEY) .map(key => { const { value, attributeName } = clusterValues[key] as PersistedAttributeValue; return { path: { endpointId, clusterId, attributeId: AttributeId(parseInt(key)), attributeName }, value, version, }; }); } async persistAttributes(attributes: DecodedAttributeReportValue<any>[]) { const endpointDataMap = new Map<EndpointNumber, Record<ClusterId, Val.Struct>>(); for (const { path: { endpointId, clusterId, attributeId, attributeName }, value, version, } of attributes) { if (value === undefined) { logger.warn(`Ignoring undefined value for attribute ${attributeId} on endpoint ${endpointId}`); continue; } let endpointData = endpointDataMap.get(endpointId); if (endpointData === undefined) { endpointData = {}; endpointDataMap.set(endpointId, endpointData); } let clusterData = endpointData[clusterId]; if (clusterData === undefined) { clusterData = {}; endpointData[clusterId] = clusterData; } clusterData[attributeId.toString()] = { value, attributeName }; clusterData[VERSION_KEY] = version; } for (const [endpointId, endpointData] of endpointDataMap) { const store = this.#storeForEndpoint(endpointId); await store.set(endpointData); } } getClusterDataVersion(endpointId: EndpointNumber, clusterId: ClusterId): number | undefined { const store = this.#storeForEndpoint(endpointId); const clusterValues = store.get.get(clusterId.toString()); return clusterValues?.[VERSION_KEY] as number | undefined; } getClusterDataVersions( filterEndpointId?: EndpointNumber, filterClusterId?: ClusterId, ): { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[] { const versions: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[] = []; const filterClusterIdStr = filterClusterId?.toString(); for (const [endpointId, store] of this.#endpointStores) { if (filterEndpointId !== undefined && endpointId !== filterEndpointId) { continue; } const endpointData = store.get; for (const clusterId of endpointData.keys()) { if (filterClusterIdStr !== undefined && clusterId !== filterClusterIdStr) { continue; } const version = endpointData.get(clusterId.toString())![VERSION_KEY]; if (typeof version === "number") { versions.push({ endpointId, clusterId: ClusterId(parseInt(clusterId)), dataVersion: version }); } } } return versions; } }