Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance FSM: Remove Redundant Initial State, Ensure Post-Transition Execution, and Update Naming #226

Merged
merged 5 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/context/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@
"clean": "rm -rfv dist *.tsbuildinfo"
},
"dependencies": {
"@alwatr/nanolib": "^5.0.0",
"@alwatr/nanolib": "^5.1.0",
"@alwatr/observable": "workspace:^"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/fetch-state-machine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@
},
"dependencies": {
"@alwatr/fsm": "workspace:^",
"@alwatr/nanolib": "^5.0.0"
"@alwatr/nanolib": "^5.1.0"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
26 changes: 12 additions & 14 deletions packages/fetch-state-machine/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import {
AlwatrFluxStateMachineBase,
type StateRecord,
type ActionRecord,
type AlwatrFluxStateMachineConfig
} from '@alwatr/fsm';
import {AlwatrFluxStateMachineBase, type StateRecord, type ActionRecord, type AlwatrFluxStateMachineConfig} from '@alwatr/fsm';
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};

export interface AlwatrFetchStateMachineConfig<S extends string> extends AlwatrFluxStateMachineConfig<S> {
export interface AlwatrFetchStateMachineConfig<S extends string> extends Omit<AlwatrFluxStateMachineConfig<S>, 'initialState'> {
fetch: Partial<FetchOptions>;
}

Expand All @@ -30,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 @@ -42,12 +37,15 @@ 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>) {
config.loggerPrefix ??= 'fetch-state-machine';
super(config);
super({
...config,
initialState: 'initial',
});
this.baseFetchOptions_ = config.fetch;
}

Expand Down Expand Up @@ -85,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
2 changes: 1 addition & 1 deletion packages/flux/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/fsm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@
"clean": "rm -rfv dist *.tsbuildinfo"
},
"dependencies": {
"@alwatr/nanolib": "^5.0.0",
"@alwatr/nanolib": "^5.1.0",
"@alwatr/observable": "workspace:^"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
35 changes: 19 additions & 16 deletions packages/fsm/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,24 @@ export abstract class AlwatrFluxStateMachineBase<S extends string, E extends str
constructor(config: AlwatrFluxStateMachineConfig<S>) {
config.loggerPrefix ??= 'flux-state-machine';
super(config);
this.message_ = {state: this.initialState_ = config.initialState};

this.initialState_ = config.initialState;
this.message_ = {state: this.initialState_};
this.resetToInitialState_();
}

/**
* Reset machine to initial state without notify.
*/
protected resetToInitialState_(): void {
this.logger_.logMethod?.('resetToInitialState_');
const from = this.message_.state;
this.message_ = {state: this.initialState_};
this.postTransition__({
from,
event: 'reset',
to: this.initialState_,
});
}

/**
Expand All @@ -54,7 +63,7 @@ export abstract class AlwatrFluxStateMachineBase<S extends string, E extends str
*/
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 @@ -70,7 +79,7 @@ export abstract class AlwatrFluxStateMachineBase<S extends string, E extends str

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 @@ -81,28 +90,22 @@ export abstract class AlwatrFluxStateMachineBase<S extends string, E extends str
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'>>>;
4 changes: 2 additions & 2 deletions packages/observable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@
"clean": "rm -rfv dist *.tsbuildinfo"
},
"dependencies": {
"@alwatr/nanolib": "^5.0.0"
"@alwatr/nanolib": "^5.1.0"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/observable/src/observable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createLogger, packageTracer} from '@alwatr/nanolib';
import {createLogger, packageTracer, type AlwatrLogger} from '@alwatr/nanolib';

import type {SubscribeOptions, ListenerCallback, Observer, SubscribeResult, AlwatrObservableInterface} from './type.js';

Expand All @@ -11,7 +11,7 @@ export interface AlwatrObservableConfig {

export abstract class AlwatrObservable<T extends DictionaryOpt = DictionaryOpt> implements AlwatrObservableInterface<T> {
protected name_;
protected logger_;
protected logger_: AlwatrLogger;
protected message_?: T;
protected observers__: Observer<this, T>[] = [];

Expand Down
4 changes: 2 additions & 2 deletions packages/remote-context/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
},
"dependencies": {
"@alwatr/fetch-state-machine": "workspace:^",
"@alwatr/nanolib": "^5.0.0"
"@alwatr/nanolib": "^5.1.0"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
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
4 changes: 2 additions & 2 deletions packages/signal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@
"clean": "rm -rfv dist *.tsbuildinfo"
},
"dependencies": {
"@alwatr/nanolib": "^5.0.0",
"@alwatr/nanolib": "^5.1.0",
"@alwatr/observable": "workspace:^"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/prettier-config": "^5.0.0",
"@alwatr/tsconfig-base": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@types/node": "^22.8.6",
"@types/node": "^22.9.0",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
Expand Down
Loading