Skip to main content

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

StoreEntitySynchronizerPage
UserStoreESPCDFUserUserStoreSynchronizerUser store
NodeStoreESPCDFNodeNodeStoreSynchronizerNode store
GroupStoreESPCDFGroupGroupStoreSynchronizerGroup store
AutomationStoreESPCDFAutomationAutomationStoreSynchronizerAutomation store
SceneStoreESPCDFSceneSceneStoreSynchronizerScene store
ScheduleStoreESPCDFScheduleScheduleStoreSynchronizerSchedule store
SubscriptionStoreSubscription 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 / exportRole
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:

  1. Wrap the entity with makeEverythingObservable (making all nested fields reactive, excluding _raw and operations).
  2. 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;

On this page