Skip to content

Commit

Permalink
refactor(fsm): update action naming conventions and enhance event han…
Browse files Browse the repository at this point in the history
…dling in state transitions

BREAKING CHANGE: all name of type ActionName in class `actionRecord_` changed
  • Loading branch information
alimd committed Nov 6, 2024
1 parent f21b562 commit c3a4094
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 55 deletions.
12 changes: 6 additions & 6 deletions packages/fetch-state-machine/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {packageTracer, fetch, type FetchOptions} from '@alwatr/nanolib';
__dev_mode__: packageTracer.add(__package_name__, __package_version__);

export type ServerRequestState = 'initial' | 'loading' | 'failed' | 'complete';
export type ServerRequestEvent = 'request' | 'requestFailed' | 'requestSucceeded';
export type ServerRequestEvent = 'request' | 'request_failed' | 'request_succeeded';

export type {FetchOptions};

Expand All @@ -25,8 +25,8 @@ export abstract class AlwatrFetchStateMachineBase<
request: 'loading',
},
loading: {
requestFailed: 'failed',
requestSucceeded: 'complete',
request_failed: 'failed',
request_succeeded: 'complete',
},
failed: {
request: 'loading',
Expand All @@ -37,7 +37,7 @@ export abstract class AlwatrFetchStateMachineBase<
} as StateRecord<ServerRequestState | ExtraState, ServerRequestEvent | ExtraEvent>;

protected override actionRecord_ = {
on_loading_enter: this.requestAction_,
on_state_loading_enter: this.requestAction_,
} as ActionRecord<ServerRequestState | ExtraState, ServerRequestEvent | ExtraEvent>;

constructor(config: AlwatrFetchStateMachineConfig<ServerRequestState | ExtraState>) {
Expand Down Expand Up @@ -83,12 +83,12 @@ export abstract class AlwatrFetchStateMachineBase<

protected requestSucceeded_(): void {
this.logger_.logMethod?.('requestSucceeded_');
this.transition_('requestSucceeded');
this.transition_('request_succeeded');
}

protected requestFailed_(error: Error): void {
this.logger_.error('requestFailed_', 'fetch_failed', error);
this.transition_('requestFailed');
this.transition_('request_failed');
}

protected setFetchOptions_(options?: Partial<FetchOptions>): void {
Expand Down
32 changes: 13 additions & 19 deletions packages/fsm/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import {packageTracer} from '@alwatr/nanolib';
import {AlwatrObservable, type AlwatrObservableConfig} from '@alwatr/observable';

import type {ActionName, ActionRecord, MachineEvent, MachineState, StateEventDetail, StateRecord} from './type.js';
import type {ActionName, ActionRecord, StateEventDetail, StateRecord} from './type.js';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);

export interface AlwatrFluxStateMachineConfig<S extends MachineState> extends AlwatrObservableConfig {
export interface AlwatrFluxStateMachineConfig<S extends string> extends AlwatrObservableConfig {
initialState: S;
}

/**
* Flux (Finite) State Machine Base Class
*/
export abstract class AlwatrFluxStateMachineBase<S extends MachineState, E extends MachineEvent> extends AlwatrObservable<{state: S}> {
export abstract class AlwatrFluxStateMachineBase<S extends string, E extends string> extends AlwatrObservable<{state: S}> {
/**
* States and transitions config.
*/
Expand Down Expand Up @@ -45,7 +45,7 @@ export abstract class AlwatrFluxStateMachineBase<S extends MachineState, E exten
this.message_ = {state: this.initialState_};
this.postTransition__({
from,
event: 'reset' as E,
event: 'reset',
to: this.initialState_,
});
}
Expand All @@ -63,7 +63,7 @@ export abstract class AlwatrFluxStateMachineBase<S extends MachineState, E exten
*/
protected async transition_(event: E): Promise<void> {
const fromState = this.message_.state;
const toState = this.stateRecord_[fromState]?.[event] ?? this.stateRecord_._all?.[event];
const toState = this.stateRecord_[fromState]?.[event];

this.logger_.logMethodArgs?.('transition_', {fromState, event, toState});

Expand All @@ -79,7 +79,7 @@ export abstract class AlwatrFluxStateMachineBase<S extends MachineState, E exten

if ((await this.shouldTransition_(eventDetail)) !== true) return;

this.notify_({state: toState});
this.notify_({state: toState}); // message update but notify event delayed after execActions.

this.postTransition__(eventDetail);
}
Expand All @@ -90,28 +90,22 @@ export abstract class AlwatrFluxStateMachineBase<S extends MachineState, E exten
private async postTransition__(eventDetail: StateEventDetail<S, E>): Promise<void> {
this.logger_.logMethodArgs?.('_transitioned', eventDetail);

await this.execAction__(`on_${eventDetail.event}`, eventDetail);
await this.execAction__(`on_event_${eventDetail.event}`, eventDetail);

if (eventDetail.from !== eventDetail.to) {
await this.execAction__(`on_state_exit`, eventDetail);
await this.execAction__(`on_${eventDetail.from}_exit`, eventDetail);
await this.execAction__(`on_state_enter`, eventDetail);
await this.execAction__(`on_${eventDetail.to}_enter`, eventDetail);
await this.execAction__(`on_any_state_exit`, eventDetail);
await this.execAction__(`on_state_${eventDetail.from}_exit`, eventDetail);
await this.execAction__(`on_any_state_enter`, eventDetail);
await this.execAction__(`on_state_${eventDetail.to}_enter`, eventDetail);
}

if (Object.hasOwn(this, `on_${eventDetail.from}_${eventDetail.event}`)) {
this.execAction__(`on_${eventDetail.from}_${eventDetail.event}`, eventDetail);
}
else {
// The action `all_eventName` is executed only if the action `fromState_eventName` is not defined.
this.execAction__(`on_all_${eventDetail.event}`, eventDetail);
}
this.execAction__(`on_state_${eventDetail.from}_event_${eventDetail.event}`, eventDetail);
}

/**
* Execute action name if defined in _actionRecord.
*/
private execAction__(name: ActionName<S, E>, eventDetail: StateEventDetail<S, E>): MaybePromise<void> {
private execAction__(name: ActionName<S, E | 'reset'>, eventDetail: StateEventDetail<S, E>): MaybePromise<void> {
const actionFn = this.actionRecord_[name];
if (typeof actionFn === 'function') {
this.logger_.logMethodArgs?.('_$execAction', name);
Expand Down
23 changes: 11 additions & 12 deletions packages/fsm/src/type.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
export interface StateEventDetail<S, E> {
export interface StateEventDetail<S extends string, E extends string> {
from: S;
event: E;
event: E | 'reset';
to: S;
}

export type StateRecord<S extends string, E extends string> = Partial<Record<S | '_all', Partial<Record<E, S>>>>;
export type StateRecord<S extends string, E extends string> = Partial<Record<S, Partial<Record<E | 'reset', S>>>>;

export type Action<S extends string, E extends string> = (eventDetail?: StateEventDetail<S, E>) => MaybePromise<void>;
export type Action<S extends string, E extends string> = (eventDetail?: StateEventDetail<S, E | 'reset'>) => MaybePromise<void>;

export type ActionName<S extends string, E extends string> =
| `on_${E}`
| `on_state_exit`
| `on_state_enter`
| `on_${S}_exit`
| `on_${S}_enter`
| `on_${S}_${E}`
| `on_all_${E}`;
| `on_event_${E}`
| `on_any_state_exit`
| `on_any_state_enter`
| `on_state_${S}_exit`
| `on_state_${S}_enter`
| `on_state_${S}_event_${E}`;

export type ActionRecord<S extends string, E extends string> = Partial<Record<ActionName<S, E>, Action<S, E>>>;
export type ActionRecord<S extends string, E extends string> = Partial<Record<ActionName<S, E | 'reset'>, Action<S, E | 'reset'>>>;
36 changes: 18 additions & 18 deletions packages/remote-context/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {packageTracer} from '@alwatr/nanolib';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);

type ExtraState = 'offlineCheck' | 'reloading' | 'reloadingFailed';
type ExtraState = 'offline_check' | 'reloading' | 'reloading_failed';
export type ServerContextState = ServerRequestState | ExtraState;

type ExtraEvent = 'cacheNotFound';
type ExtraEvent = 'cache_not_found';
export type ServerContextEvent = ServerRequestEvent | ExtraEvent;

export type AlwatrRemoteContextStateMachineConfig = AlwatrFetchStateMachineConfig<ServerContextState>;
Expand All @@ -28,37 +28,37 @@ export abstract class AlwatrRemoteContextStateMachineBase<T extends Json = Json>

this.stateRecord_ = {
initial: {
request: 'offlineCheck',
request: 'offline_check',
},
/**
* Just check offline cache data before online request.
*/
offlineCheck: {
requestFailed: 'failed',
cacheNotFound: 'loading',
requestSucceeded: 'reloading',
offline_check: {
request_failed: 'failed',
cache_not_found: 'loading',
request_succeeded: 'reloading',
},
/**
* First loading without any cached context.
*/
loading: {
requestFailed: 'failed',
requestSucceeded: 'complete',
request_failed: 'failed',
request_succeeded: 'complete',
},
/**
* First loading failed without any cached context.
*/
failed: {
request: 'loading', // //TODO: why offlineCheck? should be loading!
request: 'loading', // //TODO: why offline_check? should be loading!
},
reloading: {
requestFailed: 'reloadingFailed',
requestSucceeded: 'complete',
request_failed: 'reloading_failed',
request_succeeded: 'complete',
},
/**
* Reloading failed with previously cached context exist.
*/
reloadingFailed: {
reloading_failed: {
request: 'reloading',
},
complete: {
Expand All @@ -67,10 +67,10 @@ export abstract class AlwatrRemoteContextStateMachineBase<T extends Json = Json>
};

this.actionRecord_ = {
on_offlineCheck_enter: this.offlineRequestAction_,
on_loading_enter: this.onlineRequestAction_,
on_reloading_enter: this.onlineRequestAction_,
on_requestSucceeded: this.updateContextAction_,
on_state_offline_check_enter: this.offlineRequestAction_,
on_state_loading_enter: this.onlineRequestAction_,
on_state_reloading_enter: this.onlineRequestAction_,
on_event_request_succeeded: this.updateContextAction_,
};
}

Expand Down Expand Up @@ -101,7 +101,7 @@ export abstract class AlwatrRemoteContextStateMachineBase<T extends Json = Json>
this.logger_.logMethod?.('requestFailed_');

if (error.message === 'fetch_cache_not_found') {
this.transition_('cacheNotFound');
this.transition_('cache_not_found');
}
else {
super.requestFailed_(error);
Expand Down

0 comments on commit c3a4094

Please sign in to comment.