Overview
What This Guide Covers ?
Domain stores are MobX observable containers for CDF entities. Each store pairs with a synchronizer that listens to entity operation events and applies all state mutations via @action methods.
Access any store through the ESPCDF root instance:
import { initCDF } from "@espressif/rainmaker-base-cdf";
const espCDF = await initCDF({ sdkAdaptorRegistry });
const {
nodeStore,
groupStore,
userStore,
automationStore,
sceneStore,
scheduleStore,
} = espCDF;
For per-store entity operations, pagination, and full API, use the individual store pages linked in the table below.
Stores at a Glance
| Store | Entity | Synchronizer | Page |
|---|---|---|---|
| UserStore | ESPCDFUser | UserStoreSynchronizer | User store |
| NodeStore | ESPCDFNode | NodeStoreSynchronizer | Node store |
| GroupStore | ESPCDFGroup | GroupStoreSynchronizer | Group store |
| AutomationStore | ESPCDFAutomation | AutomationStoreSynchronizer | Automation store |
| SceneStore | ESPCDFScene | SceneStoreSynchronizer | Scene store |
| ScheduleStore | ESPCDFSchedule | ScheduleStoreSynchronizer | Schedule store |
| SubscriptionStore | — | — | Subscription store |
Common Workflows
Read store data reactively
Wrap every component that reads store data with observer:
import { observer } from "mobx-react-lite";
import type { ESPCDF } from "@espressif/rainmaker-base-cdf";
const GroupList = observer(function GroupList({ espCDF }: { espCDF: ESPCDF }) {
const groups = espCDF.groupStore.groupsList; // MobX-observable
return (
<FlatList
data={groups}
keyExtractor={(g) => g.id}
renderItem={({ item }) => <GroupRow group={item} />}
/>
);
});
Without observer, components read the value once and never re-render on changes.
Prefer granular observer components
MobX tracks at the property level. Pass individual entity or param objects into small observer sub-components:
// Only re-renders when param.value changes — not when any other param changes
const ParamToggle = observer(function ParamToggle({ param }: { param: ESPCDFDeviceParam }) {
return (
<Switch
value={param.value as boolean}
onValueChange={(val) => param.setValue(val)}
/>
);
});
Write to stores via entity methods
UI code never calls store methods directly for domain mutations. Instead it calls entity methods; the synchronizer handles the store update:
// ✅ Correct — entity method → synchronizer → store @action
const group = espCDF.groupStore.getGroupById("group-id")!;
await group.updateGroupInfo({ groupName: "Living Room" });
// ❌ Incorrect — bypasses synchronizer, skips event pipeline
espCDF.groupStore.groupsByIDMap["group-id"].name = "Living Room";
Paginate store data
Stores that fetch from a backend support cursor-based pagination. The pagination context is stored per adaptor identifier:
const state =
espCDF.groupStore.sdkAdaptorGroupsPaginationMap["rainmaker-base-sdk"];
if (state?.hasNext) {
await espCDF.groupStore.fetchNextPageGroupsForSDK("rainmaker-base-sdk");
}
Clear stores on logout
user.logout() triggers UserStoreSynchronizer to clear group, node, automation, scene, and schedule stores. You rarely need manual clear() calls unless you reset state without logging out.
Each store’s clear() also calls synchronizer.detach for every entity, preventing memory leaks.
Advanced Concepts
ESPCDF root helpers
| Method / export | Role |
|---|---|
initCDF(config) | Validates config and returns the singleton ESPCDF instance |
ESPCDF.getInstance() | Access singleton after first initCDF |
adaptor(identifier) | Resolve a registered ESPSDKAdaptor |
getActiveAdaptorIdentifier() | Active adaptor id or null |
getCurrentHome() | ESPCDFGroup for groupStore.currentHomeId |
getNodesForCurrentHome() | Nodes in the current home’s nodeIds |
addStore(name, StoreClass) | Attach a custom MobX store to the root instance |
How stores and synchronizers connect
Every entity enters a store through an add* or set*List method. These methods:
- Wrap the entity with
makeEverythingObservable(making all nested fields reactive, excluding_rawandoperations). - Call
synchronizer.attach(entity)to set up the event subscription.
When an entity is removed, synchronizer.detach(entityId) cleans up the subscription:
Never insert entities directly into store maps. Always go through the store's public add* / set*List methods so the synchronizer subscription is set up correctly.
Extend a store with custom properties
UserStore, GroupStore, and SceneStore expose addProperty to attach a custom observable field at runtime:
espCDF.groupStore.addProperty("lastSyncedAt", null as Date | null);
(espCDF.groupStore as any).lastSyncedAt = new Date();
addProperty uses extendObservable under the hood, so MobX tracks changes to the new property automatically. For other stores, add fields on a custom store via ESPCDF.addStore.
Add a custom store
Attach a fully custom domain store to the root ESPCDF instance without modifying CDF core:
import { makeAutoObservable, action } from "mobx";
import type { ESPCDF } from "@espressif/rainmaker-base-cdf";
class FavoritesStore {
favoriteNodeIds: string[] = [];
constructor(public rootStore: ESPCDF) {
makeAutoObservable(this);
}
@action addFavorite(nodeId: string) {
if (!this.favoriteNodeIds.includes(nodeId)) {
this.favoriteNodeIds.push(nodeId);
}
}
@action removeFavorite(nodeId: string) {
this.favoriteNodeIds = this.favoriteNodeIds.filter((id) => id !== nodeId);
}
get favoriteNodes() {
return this.favoriteNodeIds
.map((id) => this.rootStore.nodeStore.getNodeById(id))
.filter(Boolean);
}
}
// Mount it on the root store
espCDF.addStore("favoritesStore", FavoritesStore);
// Access it later
const favStore = (espCDF as any).favoritesStore as FavoritesStore;
Related Resource
- Entities — entity wrapper, catalog,
OperationEventEmitter - Store source (esp-rainmaker-app-cdf-ts)
- Errors — config and registry error handling
- API Reference: