@mirta/store
v0.5.1
Published
Type-safe store for wb-rules
Readme
@mirta/store
Типизированное хранилище состояний для сценариев автоматизации, вдохновлённое архитектурой Pinia.
Каждый скрипт в папке wb-rules выполняется в изолированном контексте — с отдельным пространством имён. Это означает, что функции и переменные одного скрипта недоступны другим.
@mirta/store позволяет выносить данные в централизованные состояния, доступные из любых скриптов и модулей. Предоставляет удобный API для:
- определения структуры состояния,
- типизированного доступа,
- реактивного обновления,
- изоляции экземпляров.
Работает на контроллерах Wiren Board, поддерживает TypeScript и совместим с wb-rules.
📦 Установка
pnpm add @mirta/store✅ Пакет проходит сборку конфигурацией из пакета @mirta/rollup и при вызове в коде автоматически встраивается как модуль wb-rules-modules.
🚀 Быстрый старт
1. Задайте структуру хранилища
Используйте defineStore для описания структуры. Делайте это один раз, лучше в модуле.
// src/wb-rules-modules/counter.ts
import { defineStore } from '@mirta/store'
export const useCounter = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
setCount(value: number) {
this.count = value
},
},
})2. Используйте в скриптах и модулях
Подключите хранилище в любом скрипте wb-rules или модуле wb-rules-modules — состояние будет общим.
// src/wb-rules/01-init.ts
import { useCounter } from '#wbm/counter'
const store = useCounter()
log(`Счётчик: ${store.count}`) // 0
store.increment()Изменения в одном скрипте мгновенно доступны в другом.
📚 API
defineStore(typeId, options)
Создаёт определение хранилища.
typeId: string— идентификатор типа хранилища (должен быть уникальным),options: DefineStoreOptions— конфигурация:state,getters,actions.
❗ Повторный вызов с тем же
typeIdвызывает ошибкуStoreError— определение уникально.
Возвращает функцию useStore().
useStore(scope?)
Возвращает экземпляр хранилища.
scope?: string— опциональный идентификатор контекста (например,'kitchen').
Если scope указан, storeId = "${typeId}/${scope}".
Если нет — используется общий storeId = typeId.
Свойства экземпляра
| Свойство | Тип | Описание |
|--------|-----|----------|
| $id | string | Уникальный идентификатор экземпляра |
| $state | TState | Ссылка на состояние |
| $patch | (patch: Partial<TState>) => void(mutator: (state: TState) => void) => void | Обновляет состояние |
| $reset | () => void | Сбрасывает состояние к начальному |
StoreError
Специализированный класс ошибок с кодами:
'alreadyDefined'— повторное определение,'alreadyDefinedOutside'— повторное определение в другом файле,'readonlyProperty'— изменение служебного поля,'unknownProperty'— обращение к неизвестному полю.
🔧 Особенности
✅ Полная типобезопасность
Автодополнение, проверка типов, поддержка this в геттерах и действиях.
getters: {
double: (state) => state.count * 2,
doublePlusOne(): number { // ← явный тип обязателен
return this.double + 1
}
},
actions: {
increment() {
this.count++
this.$patch({ count: 5 })
}
}⚠️ Геттеры, использующие
this, должны иметь явный возвращаемый тип.
📦 Глубокое обновление через $patch
Обновляйте вложенные объекты безопасно — @mirta/store выполняет глубокое слияние.
Поддерживается два способа:
store.$patch({ count: 10, config: { debug: true } })
store.$patch((state) => {
state.tags.push('new')
})Использует
deepMerge: объекты сливаются, массивы перезаписываются.
🔁 Сброс состояния через $reset
Верните хранилище к начальному состоянию — как при первом создании.
store.$reset()- Вызывает
state() - Сохраняет реактивность
- Удаляет значения состояния для тестов, перезапуска логики, сброса настроек
🛑 Защита от дублирования
Хранилище нельзя определить дважды с одним typeId — это предотвращает конфликты.
defineStore('sensor', { ... }) // ✅
defineStore('sensor', { ... }) // ❌ StoreError: alreadyDefinedПроверка работает всегда — в
developmentиproduction.
Гарантирует, что только один модуль может контролировать тип хранилища.
🧩 Scoped States — изолированные экземпляры
Создавайте отдельные экземпляры хранилища для разных контекстов: комнат, устройств, сессий.
const useSensor = defineStore('sensor', { ... })
const kitchen = useSensor('kitchen')
const bathroom = useSensor('bathroom')Каждый экземпляр имеет storeId = "sensor/kitchen" — состояние изолировано.
Полезно при управлении множеством однотипных сущностей.
🔐 Internal Store — инкапсуляция состояния
Сделайте хранилище недоступным извне, не экспортируя useStore().
Если useStore() не экспортирован:
- внешние модули не могут получить доступ к состоянию,
- повторное определение с тем же
typeIdзапрещено, - состояние становится внутренней деталью реализации.
Полезно в NPM-пакетах, где нужно скрыть реализацию.
❗ Не имеет смысла в локальных модулях, где
defineStore()иuseStore()в одном файле.
💾 Сериализация состояния
Для сохранения или передачи состояния используйте $state.
const store = useCounter()
// ✅ Правильно — сериализует только состояние
const json = JSON.stringify(store.$state)
// ❌ Неправильно — содержит функции и служебные поля
const json = JSON.stringify(store)Свойство $state содержит чистый объект состояния без методов и прокси-данных.
🔄 Когда что использовать
1. Временное состояние: @mirta/store vs global.__proto__
| Характеристика | @mirta/store | global.__proto__ |
|----------------|----------------|--------------------|
| Инкапсуляция | ✔️ | ❌ |
| Типизация | ✔️ | ❌ |
| API ($patch, $reset) | ✔️ | ❌ |
| Изоляция экземпляров | ✔️ useStore('kitchen') | ❌ |
| Читаемость | ✔️ | ❌ |
| Производительность | ✔️ Минимальный оверхед | ✔️ Прямой доступ |
❌
global.__proto__— антипаттерн. Не используйте.
2. Временное vs постоянное хранение
| Характеристика | @mirta/store | PersistentStorage |
|----------------|----------------|---------------------|
| Хранение | RAM | Flash / FS |
| После перезагрузки | ❌ | ✔️ |
| Типизация | ✔️ | ❌ |
| Скорость | Высокая | Ниже (IO) |
| API | $patch, $reset | get, set, remove |
✅ Используйте совместно:
@mirta/store— для текущего состояния,PersistentStorage— для сохранения и восстановления.
🧪 Тестирование
Пакет полностью покрыт модульными тестами с использованием @mirta/testing и vitest.
Тесты проверяют:
- Создание и использование хранилищ
- Работу
$patch,$reset - Поддержку изолированных экземпляров
- Защиту от дублирования
⚠️ Ограничения
- Состояние теряется при перезагрузке сервиса
wb-rules.serviceили контроллера. - Экземпляры хранилищ кэшируются глобально и не удаляются автоматически. При динамическом создании большого количества экземпляров с уникальными
scopeможет возникнуть утечка памяти. Рекомендуется использовать предсказуемые, ограниченные значенияscope.
