Changelog
История релизов lite-fsm.
2.0.0
Крупный релиз с flat actors: actor templates остаются обычными машинами, а MachineManager умеет запускать их как плоские runtime actors с маршрутизацией, lifecycle-состояниями и совместимостью с middleware.
Monorepo packages
Проект перешёл от единого пакета lite-fsm с subpath entrypoints к монорепе с отдельными scoped-пакетами:
@lite-fsm/core@2.0.0— framework-agnostic runtime:createMachine,MachineManager, effects и core types.@lite-fsm/react@2.0.0— React provider, context и hooks.@lite-fsm/middleware@2.0.0— optional middleware integrations: DevTools и Immer.@lite-fsm/persist@1.0.0— persistence helpers и React hooks для restore/save lifecycle.@lite-fsm/graph@0.1.0— alpha graph compiler, analyzer, simulator и visualizer view-model.@lite-fsm/cli@0.1.0— alpha CLI с командойlite-fsm export-graph.
Flat actors
- BREAKING: имена состояний
__INIT,__RESOLVED,__REJECTED,__CANCELLEDзарезервированы для actor templates и terminal lifecycle. - BREAKING: события
MachineManagerиспользуютManagerAction<P>с опциональным routingmeta. - Машина с
__INITвconfigтеперь считается actor template и может создавать плоские runtime actors внутриMachineManager. - Добавлена маршрутизация через
meta.actorId,meta.groupId,meta.groupTag, actor effectselfи transition sugar для actor events. - BREAKING: runtime actor slice теперь имеет форму
{ state, context, meta }; persisted actor template snapshots безslice.metaбольше не подходят дляreplaceReducer/DevTools JUMP/future hydration restore. - Добавлен
createActorMeta()для ручной сборки readonly actor identity в replacement/hydration input. - Actor targets
__RESOLVED,__REJECTED,__CANCELLEDсхлопываются в terminal lifecycle. createEffect,condition(), Immer reducer support и DevTools time-travel стали actor-aware через publicslice.meta.- Удалены internal DevTools restore exports/transport:
actorDevtools.ts,DEVTOOLS_API,ACTOR_RESTORE,__liteFsmActor. - Actor templates остаются runtime-only для hydration:
dehydrate()пропускает templates, явный actor dehydrate бросает ошибку, а hydrate snapshots с actor keys в DEV пропускаются с warning. - Domain-only machines сохраняют прежнюю форму
{ state, context }и runtime-поведение.
Migration для persisted actor records: пересоздать snapshot после обновления или добавить meta вручную через createActorMeta({ actorId: recordKey, groupId, groupTag }).
Spawn id customization
- Добавлены опции
MachineManagerOptions.originId,generateActorId,generateGroupIdдля P2P, multi-tab, server↔client и шард-сценариев распределенного спавна. originId(строка без#) добавляется ко всем созданным менеджером id в формате${originId}#${templateKey}/${counter}; чужие id (с другим owner-префиксом или без него) приhydrateне двигают локальные счетчики.generateActorIdиgenerateGroupIdпринимаютSpawnIdContext<P>({ templateKey, groupTag, counter, originId, action }) и возвращают доменный id, например${originId}#player/${userId}. Counter инкрементируется всегда; collision-check блокирует duplicate id через новый кодLITE_FSM_INVALID_GENERATED_ID.- Невалидный
originId(пустой или содержащий#) бросаетLITE_FSM_INVALID_OPTIONS. - Изменение обратно совместимое: менеджер без новых опций продолжает выдавать
templateKey/0и принимать любые opaque external id вhydrate.
Persist
- Добавлены новые entrypoints
@lite-fsm/persistи@lite-fsm/persist/react; core entrypoint и@lite-fsm/reactне импортируют persist-слой. persistManager(manager, opts)создаёт typed controller дляrestore,save,flush,clear, status snapshots и подписок на manager/storage поверхMachineManager.dehydrate()иhydrate().- Добавлен
createJsonStorage({ key, storage })дляlocalStorage/sessionStorage-совместимых JSON-хранилищ и публичныйPersistStorage<S>для custom async storage. start()стал ref-counted lifecycle API: он запускает background restore и подписки, не блокирует SSR/hydration, а cleanup останавливает подписки и отложенные saves.- Restore валидирует envelope persist-слоя, применяет snapshot через
hydrateсо strategy по умолчанию"merge"и удаляет invalid, expired или несовместимые records. - Persist options покрывают выбор
machines,strategy,storageVersion,maxAge,throttleMs,shouldSave,migrate,onRestoreSettledиonError. storageVersionmismatch безmigrateудаляет запись; успешныйmigrate(record)возвращает новыйMachineManagerSnapshotи сразу переписывает storage record в актуальном формате.throttleMsсклеивает частые saves,flush()принудительно дописывает pending save, аclear()отменяет pending save, удаляет record и сбрасывает status.- Добавлены React hooks
usePersistStatus(controller)иuseIsPersistRestoring(controller)для UI поверхPersistController;FSMContextProviderпринимает structuralpersistlifecycle controller или массив controllers.
1.2.0
В этой версии добавлен snapshot/SSR hydration API, а типовая система стала строже и проще для сопровождения.
Snapshot / SSR hydration
MachineManagerполучилgetSnapshot(),getHydratedState(),hydrate()иdehydrate()с envelope-формой{ schemaVersion, machines }.- Машины могут задавать
hydrateиdehydratehooks для custom transport shape, merge/replace политики и content-noop идемпотентности. hydrateне проходит через middleware и не запускает effects; подписчики видят system action@@lite-fsm/HYDRATE.- React entrypoint получил
useHydrateSnapshotиFSMHydrationBoundary: boundary даётuseSelectorвременный snapshot уже на первом render, а затем до первого paint переносит snapshot в настоящий manager. - Playground содержит новый
/ssr-demo-3как reference для manifest-first SSR на snapshot API.
Сервер может собрать transport snapshot:
const snapshot = manager.dehydrate({ machines: ["profileSession"] });Клиент может восстановить его напрямую:
manager.hydrate(snapshot);Для React SSR/RSC удобнее обернуть subtree:
<FSMHydrationBoundary snapshot={snapshot}>
<Profile />
</FSMHydrationBoundary>Boundary можно вкладывать под Suspense: дочерний snapshot считается поверх родительского preview, поэтому widget видит и grid seed, и свой seed на первом render.
<FSMHydrationBoundary snapshot={gridSnapshot}>
{items.map((item) => (
<Suspense key={item.id} fallback={<WidgetSkeleton />}>
<FSMHydrationBoundary snapshot={createWidgetSnapshot(item)}>
<Widget item={item} />
</FSMHydrationBoundary>
</Suspense>
))}
</FSMHydrationBoundary>Машина сама решает, как мержить и когда не будить подписчиков:
hydrate: (prev, snapshot) => {
if (prev.context.version === snapshot.version) return prev;
return { ...prev, context: { ...prev.context, ...snapshot } };
};Типы и DX
FSMEventтеперь корректно различает события с payload и без payload, нормально распределяется по union имён событий и схлопывается вneverдляFSMEvent<never>.- Типы effects стали точнее: orphan state больше не получает весь union событий, если в состояние нет входящих переходов.
- Пустые зависимости менеджера выводятся как
{}, а неunknown. - React hooks больше не проваливаются в silent
any: базовыеuseManagerиuseTransitionполучают безопасные defaults. - Defaults для middleware стали безопаснее:
MiddlewareиспользуетunknownиAnyEvent, а reusable middleware можно описывать черезGenericMiddleware. - Внутренняя типовая модель упрощена вокруг
StateType,StateName,IncomingEventTypesи связанных helper-типов; часть внутренних строительных типов убрана из публичного экспорта.
Примеры изменений
FSMEvent<"SAVE", any> больше не делает payload опциональным:
// До
type E = FSMEvent<"SAVE", any>;
// { type: "SAVE"; payload?: any }
// После
type E = FSMEvent<"SAVE", any>;
// { type: "SAVE"; payload: any }Union имён событий теперь становится discriminated union:
// До
type E = FSMEvent<"START" | "STOP">;
// { type: "START" | "STOP" }
// После
type E = FSMEvent<"START" | "STOP">;
// { type: "START" } | { type: "STOP" }FSMEvent<never> больше не создаёт бесполезный объект:
// До
type E = FSMEvent<never>;
// { type: never }
// После
type E = FSMEvent<never>;
// neverOrphan state больше не получает весь union событий:
// До
type Action = DefaultDeps<"orphan", Config, Event>["action"];
// Event
// После
type Action = DefaultDeps<"orphan", Config, Event>["action"];
// neverПустые зависимости менеджера стали понятнее:
// До
type Deps = MachineDependencies<{}>;
// unknown
// После
type Deps = MachineDependencies<{}>;
// {}React hooks и middleware больше не проваливаются в silent any:
// До
useManager();
// IMachineManager<any, any>
useTransition();
// (payload: any) => any
type M = Middleware;
// Middleware<any, { type: any; payload?: any }>
// После
useManager();
// IMachineManager<MachineStore, AnyEvent>
useTransition();
// (payload: AnyEvent) => AnyEvent
type M = Middleware;
// Middleware<unknown, AnyEvent>Для строгого app-level API типы по-прежнему можно фиксировать явно:
const manager = useManager<AppMachines, AppEvent>();
const transition = useTransition<AppEvent>();Reusable middleware теперь описывается отдельно:
type M = GenericMiddleware;1.1.0-beta.2 - 25 сентября 2025
- Второй beta-релиз ветки
1.1.0.
1.1.0-beta.1 - 5 мая 2025
- Первый beta-релиз ветки
1.1.0.
1.0.0 - 27 января 2025
- Первый стабильный релиз
lite-fsm.
0.0.6 - 23 ноября 2023
- Поддерживающий релиз ранней ветки
0.0.x.
0.0.5 - 10 октября 2023
- Поддерживающий релиз ранней ветки
0.0.x.
0.0.4 - 23 июня 2023
- Поддерживающий релиз ранней ветки
0.0.x.
0.0.3 - 23 июня 2023
- Поддерживающий релиз ранней ветки
0.0.x.
0.0.2 - 23 июня 2023
- Поддерживающий релиз ранней ветки
0.0.x.
0.0.1 - 21 июня 2023
- Первый npm-релиз
lite-fsm.
До 0.0.1 - 2022
- В 2022 году идея библиотеки проходила проверку в production: подход тестировался поверх Redux Toolkit в видеоплеере для телеканалов с высокой нагрузкой.