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

Refactor and update all packages #198

Merged
merged 12 commits into from
Sep 15, 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
29 changes: 13 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
# Alwatr Signal
# Alwatr Flux - Elegant State Management and Event System

## Introduction

Elegant powerful event system for handle global signals and states base on observable design pattern, written in tiny TypeScript module.
Alwatr Flux empowers your applications with elegant and powerful state management and event handling capabilities. Built on the observable design pattern, Flux provides a lightweight yet robust foundation for managing global signals and states.

Every signal has own value and can be used as a advance **state management** like redux and recoil without the complexities and unnecessary facilities of those libraries.
**Key Features:**

Contains the following packages:
- **Intuitive State Management:** Embrace Flux as an advanced alternative to Redux or Recoil, minus the complexities and unnecessary overhead. Each signal maintains its own value, offering seamless state control.
- **Finite-State Machines (FSM):** Leverage observables to gracefully manage invocations and state transitions within your finite-state machines.
- **Server Context Management:** Effortlessly handle server-side context with Flux's elegant context manager, ensuring optimal organization and control.
- ...

1. [Finite-state machines (FSM)](./packages/logger): Managing invocations finite-state machines base on observable signal pattern.
2. [Server Context](./packages/server-context): Elegant powerful server-context manager base on alwatr signal.
3. [Signal](./packages/signal): Elegant powerful event system for handle global signals and states.

<!-- @TODO: update this list-->
## Usage

## Installation
Refer to the individual package READMEs for comprehensive usage instructions and examples.

```bash
npm install @alwatr/module-name
```
## Contributing

## Usage

Follow each package's README for usage instructions.
Contributions are welcome! Please consult the CONTRIBUTING guidelines for detailed information on how to get involved.

## License

[MIT](./LICENSE)

```
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "alwatr-signal",
"description": "Elegant powerful event system for handle global signals and states base on observable design pattern, written in tiny TypeScript module.",
"repository": "https://github.com/Alwatr/signal",
"name": "alwatr-flux",
"description": "Alwatr Flux empowers your applications with elegant and powerful state management and event handling capabilities. Built on the observable design pattern, Flux provides a lightweight yet robust foundation for managing global signals and states.",
"repository": "https://github.com/Alwatr/flux",
"author": "S. Ali Mihandoost <[email protected]> (https://ali.mihandoost.com)",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -60,5 +60,5 @@
"prettier": "^3.3.3",
"typescript": "^5.6.2"
},
"packageManager": "yarn@4.4.1"
"packageManager": "yarn@4.5.0"
}
2 changes: 2 additions & 0 deletions packages/fsm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@
},
"dependencies": {
"@alwatr/logger": "^3.2.12",
"@alwatr/polyfill-has-own": "1.0.8",
"@alwatr/signal": "workspace:^"
},
"devDependencies": {
"@alwatr/nano-build": "^1.3.8",
"@alwatr/prettier-config": "^1.0.4",
"@alwatr/tsconfig-base": "^1.2.0",
"@alwatr/type-helper": "^1.2.5",
"@types/node": "^22.5.5",
"jest": "^29.7.0",
"typescript": "^5.6.2"
Expand Down
89 changes: 43 additions & 46 deletions packages/fsm/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,64 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {definePackage} from '@alwatr/logger';
import '@alwatr/polyfill-has-own';
import {AlwatrObservable} from '@alwatr/signal';

import type {ActionName, ActionRecord, StateEventDetail, StateRecord} from './type.js';
import type {MaybePromise} from '@alwatr/type';
import type {} from '@alwatr/nano-build';
import type {MaybePromise} from '@alwatr/type-helper';

definePackage('fsm', '2.x');
definePackage('@alwatr/signal', __package_version__);

/**
* Finite State Machine Base Class
*/
export abstract class FiniteStateMachineBase<S extends string, E extends string> extends AlwatrObservable<S> {
/**
* Current state
*/
protected get _state(): S {
return this._getData()!;
}

/**
* States and transitions config.
*/
protected _stateRecord: StateRecord<S, E> = {};
protected stateRecord_: StateRecord<S, E> = {};

/**
* Bind actions name to class methods
*/
protected _actionRecord: ActionRecord<S, E> = {};
protected actionRecord_: ActionRecord<S, E> = {};

protected _initialState: S;
protected initialState_: S;

protected override data_: S;

constructor(config: {name: string; loggerPrefix?: string; initialState: S}) {
config.loggerPrefix ??= 'fsm';
super(config);
this._initialState = config.initialState;
this._reset();
this.data_ = this.initialState_ = config.initialState;
}

/**
* Reset machine to initial state.
*/
protected resetToInitialState_(): void {
this.logger_.logMethod?.('resetToInitialState_');
this.data_ = this.initialState_;
}

/**
* Transition condition.
*/
protected _shouldTransition(_eventDetail: StateEventDetail<S, E>): MaybePromise<boolean> {
this._logger.logMethodFull?.('_shouldTransition', _eventDetail, true);
protected shouldTransition_(_eventDetail: StateEventDetail<S, E>): MaybePromise<boolean> {
this.logger_.logMethodFull?.('shouldTransition_', _eventDetail, true);
return true;
}

/**
* Transition finite state machine instance to new state.
*/
protected async _transition(event: E): Promise<void> {
const fromState = this._state;
const toState = this._stateRecord[fromState]?.[event] ?? this._stateRecord._all?.[event];
protected async transition_(event: E): Promise<void> {
const fromState = this.data_;
const toState = this.stateRecord_[fromState]?.[event] ?? this.stateRecord_._all?.[event];

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

if (toState == null) {
this._logger.incident?.('transition', 'invalid_target_state', {
this.logger_.incident?.('transition', 'invalid_target_state', {
fromState,
event,
});
Expand All @@ -64,51 +67,45 @@ export abstract class FiniteStateMachineBase<S extends string, E extends string>

const eventDetail: StateEventDetail<S, E> = {from: fromState, event, to: toState};

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

this._notify(toState);
this.notify_(toState);

this._transitioned(eventDetail);
this.postTransition__(eventDetail);
}

/**
* Execute all actions for current state.
*/
protected async _transitioned(eventDetail: StateEventDetail<S, E>): Promise<void> {
this._logger.logMethodArgs?.('_transitioned', eventDetail);
protected async postTransition__(eventDetail: StateEventDetail<S, E>): Promise<void> {
this.logger_.logMethodArgs?.('_transitioned', eventDetail);

await this._$execAction(`_on_${eventDetail.event}`, eventDetail);
await this.execAction__(`_on_${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_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);
}

if (`_on_${eventDetail.from}_${eventDetail.event}` in this) {
this._$execAction(`_on_${eventDetail.from}_${eventDetail.event}`, eventDetail);
if (Object.hasOwn(this, `_on_${eventDetail.from}_${eventDetail.event}`)) {
this.execAction__(`_on_${eventDetail.from}_${eventDetail.event}`, eventDetail);
}
else {
this._$execAction(`_on_all_${eventDetail.event}`, eventDetail);
// The action `all_eventName` is executed only if the action `fromState_eventName` is not defined.
this.execAction__(`_on_all_${eventDetail.event}`, eventDetail);
}
}

/**
* Execute action name if defined in _actionRecord.
*/
protected _$execAction(name: ActionName<S, E>, eventDetail: StateEventDetail<S, E>): MaybePromise<void> {
const actionFn = this._actionRecord[name];
protected execAction__(name: ActionName<S, E>, eventDetail: StateEventDetail<S, E>): MaybePromise<void> {
const actionFn = this.actionRecord_[name];
if (typeof actionFn === 'function') {
this._logger.logMethodArgs?.('_$execAction', name);
return this._actionRecord[name]?.call(this, eventDetail);
this.logger_.logMethodArgs?.('_$execAction', name);
return actionFn.call(this, eventDetail);
}
}

/**
* Reset machine to initial state.
*/
protected _reset(): void {
this._$data = this._initialState;
}
}
6 changes: 3 additions & 3 deletions packages/fsm/src/fsm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export abstract class FiniteStateMachine<S extends string, E extends string> ext
/**
* Current state.
*/
get state(): S {
return super._state;
getState(): S {
return this.data_;
}

/**
* Transition finite state machine instance to new state.
*/
transition(event: E): void {
super._transition(event);
this.transition_(event);
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/fsm/src/type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {MaybePromise} from '@alwatr/type';
import type {MaybePromise} from '@alwatr/type-helper';

export interface StateEventDetail<S, E> {
from: S;
Expand Down
5 changes: 3 additions & 2 deletions packages/server-context/src/server-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {fetch} from '@alwatr/fetch';
import {ActionRecord, FiniteStateMachineBase, StateRecord} from '@alwatr/fsm';
import {definePackage} from '@alwatr/logger';

import type {FetchOptions} from '@alwatr/fetch/type.js';
import type {FetchOptions} from '@alwatr/fetch';
import type {} from '@alwatr/nano-build';

definePackage('server-context', '2.x');
definePackage('@alwatr/signal', __package_version__);

export interface ServerRequestConfig extends Partial<FetchOptions> {
name: string;
Expand Down
16 changes: 9 additions & 7 deletions packages/signal/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {AlwatrObservable} from './observable.js';

import type { Dictionary } from '@alwatr/type-helper';

/**
* Alwatr context signal.
* Alwatr Context.
*/
export class AlwatrContextSignal<T> extends AlwatrObservable<T> {
export class AlwatrContext<T extends Dictionary> extends AlwatrObservable<T> {
constructor(config: {name: string; loggerPrefix?: string}) {
config.loggerPrefix ??= 'context-signal';
super(config);
Expand All @@ -15,15 +17,15 @@ export class AlwatrContextSignal<T> extends AlwatrObservable<T> {
* Return undefined if context not set before or expired.
*/
getValue(): T | undefined {
return super._getData();
return this.data_;
}

/**
* Set context value and notify all subscribers.
*/
setValue(value: T): void {
this._logger.logMethodArgs?.('setValue', {value});
super._notify(value);
this.logger_.logMethodArgs?.('setValue', {value});
super.notify_(value);
}

/**
Expand All @@ -32,13 +34,13 @@ export class AlwatrContextSignal<T> extends AlwatrObservable<T> {
* `receivePrevious` in new subscribers not work until new context changes.
*/
expire(): void {
super._clear();
super.clearData_();
}

/**
* Get the value of the next context changes.
*/
untilChange(): Promise<T> {
return super._untilNewNotify();
return super.untilNewNotify_();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export * from './observable.js';
export * from './simple-signal.js';
export * from './signal.js';
export * from './context.js';
export * from './multithread-context.js';
// export * from './multithread-context.js';
export type * from './type.js';
12 changes: 6 additions & 6 deletions packages/signal/src/multithread-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {createLogger} from '@alwatr/logger';

import {AlwatrContextSignal} from './context.js';
import {AlwatrContext} from './context.js';

interface AlwatrContextChangedMessage {
type: 'alwatr_context_changed';
Expand All @@ -11,7 +11,7 @@ interface AlwatrContextChangedMessage {
/**
* Alwatr multithread context signal.
*/
export class AlwatrMultithreadContextSignal<TValue> extends AlwatrContextSignal<TValue> {
export class AlwatrMultithreadContextSignal<TValue> extends AlwatrContext<TValue> {
protected static _logger = createLogger(`alwatr/mt-context`);
protected static _worker?: Worker;
protected static _registry: Record<string, AlwatrMultithreadContextSignal<unknown> | undefined> = {};
Expand All @@ -29,7 +29,7 @@ export class AlwatrMultithreadContextSignal<TValue> extends AlwatrContextSignal<
if (context === undefined) {
throw new Error('context_not_define', {cause: 'context not define in this thread yet!'});
}
context._notify(message.payload);
context.notify_(message.payload);
}

static _postMessage(name: string, payload: unknown): void {
Expand All @@ -47,18 +47,18 @@ export class AlwatrMultithreadContextSignal<TValue> extends AlwatrContextSignal<
constructor(config: {name: string; loggerPrefix?: string}) {
super(config);

if (AlwatrMultithreadContextSignal._registry[this._name] !== undefined) {
if (AlwatrMultithreadContextSignal._registry[this.name_] !== undefined) {
throw new Error('context_name_exist');
}

AlwatrMultithreadContextSignal._registry[this._name] = this as AlwatrMultithreadContextSignal<unknown>;
AlwatrMultithreadContextSignal._registry[this.name_] = this as AlwatrMultithreadContextSignal<unknown>;
}

/**
* Set context value and notify all subscribers.
*/
override setValue(value: TValue): void {
super.setValue(value);
AlwatrMultithreadContextSignal._postMessage(this._name, value);
AlwatrMultithreadContextSignal._postMessage(this.name_, value);
}
}
Loading
Loading