reactive-actor
Version:
Actor model implementation with rxjs
140 lines • 5.83 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.eventStateMachine = exports.eventReducer = exports.createStateMachineReducer = exports.Actor = exports.stop = void 0;
const tslib_1 = require("tslib");
const rxjs_1 = require("rxjs");
const of_type_operator_1 = require("../operators/of-type.operator");
const util_1 = require("../util");
exports.stop = (0, util_1.createEvent)('REACTIVE_ACTOR_STOP');
/**
* Reference:
* https://www.youtube.com/watch?v=7erJ1DV_Tlo&ab_channel=jasonofthel33t
*
* Actor:
* Fundamental unit of computation
* Actor needs to embody
* - Processing
* - Storage
* - Communication
*
* "One ant is no ant" - "One human is no human" - "One actor is no actor"
* Actors live within systems
*
* Fundamental Properties:
* - Receive messages, in response of this it can"
* - Create actors
* - Send messages (to other actors and itself)
* - Designates what to do with the next message it receives
*/
class Actor {
constructor(address) {
this.address = address;
this.message$ = new rxjs_1.Subject();
/**
* Stream of stop events (might be useful to know when the actor stops)
*/
this.stop$ = this.message$.pipe((0, of_type_operator_1.ofType)(exports.stop));
}
/**
* Method to send messages to the actor
* @param message we want to send to the actor
*/
send(message) {
this.message$.next(message);
}
/**
* Subscribes to an observable of events when there is an emission will send the event to a
* recipient (actor reference) when specified and to itself when not.
* completes when stop message is send to the actor.
* @param messages an observable of messages
* @example
*
* import { Actor, createEvent } from 'reactive-actor';
*
* export const getUsers = createEvent('[Users] Get Users');
*
* export const getUsersSuccess = createEvent<GitHubUser[]>(
* '[Users] Get Users Success'
* );
*
* export const getUsersFail = createEvent<string>('[Users] Get Users Fail');
*
*
* export type UsersActorEvents = ReturnType<typeof getUsers>;
*
* export class UsersActor extends Actor<UsersActorEvents> {
* // Reference to logger actor
* private readonly logger = new Actor('logger');
*
* // Recipient is not define thus will send the resulting event to itself
* private readonly getUsers$ = this.messages$.pipe(
* ofType(getUsers),
* exhaustMap(() =>
* ajax<GitHubUser[]>(`https://api.github.com/users?per_page=5`).pipe(
* map(({ response }) => getUsersSuccess(response)),
* catchError((err) => of(getUsersFail(err)))
* )
* )
* );
*
* // Recipient is define thus will send event (getUsersFail) to specified recipient (logger)
* private readonly getUsersFail$ = this.messages$.pipe(
* ofType(getUsersFail),
* addRecipient(this.logger)
* );
*
* constructor() {
* super('users');
* this.answer(this.getUsers$, this.getUsersFail$);
* }
* }
*/
answer(...messages) {
const answers$ = (0, rxjs_1.merge)(...messages).pipe((0, rxjs_1.tap)((_a) => {
var { recipient } = _a, message = tslib_1.__rest(_a, ["recipient"]);
return recipient ? recipient.send(message) : this.send(message);
}), (0, rxjs_1.takeUntil)(this.stop$));
answers$.subscribe();
}
/**
* Actor message stream, allows to define actor behavior
* see [example](https://www.npmjs.com/package/reactive-actor)
*/
get messages$() {
return this.message$.asObservable();
}
}
exports.Actor = Actor;
/**
* Creates an event reducer from a state machine defined as a dictionary see: ``EventStateMachineStructure``
* @param stateMachine a state machine defined as a dictionary
* @returns reducer function that process the next state based on current state and an event
*/
function createStateMachineReducer(stateMachine) {
return (state, message) => { var _a, _b; return (_b = (_a = stateMachine[state]) === null || _a === void 0 ? void 0 : _a[message.type]) !== null && _b !== void 0 ? _b : state; };
}
exports.createStateMachineReducer = createStateMachineReducer;
/**
* Creates a function that once applied to a stream of events will reduce state and events over time
* similar to rxjs ``scan`` operator but will multicast values to subscribers
* @param reducer - a function with the form of ``(state: TState, event: TEvent) => TState``
* ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
* @param initialState state that will be reduced will the first event or state at time 0
* @returns operator function to reduce state and events over time
*/
function eventReducer(reducer, initialState) {
return (source$) => source$.pipe((0, rxjs_1.scan)(reducer, initialState), (0, rxjs_1.share)({ connector: () => new rxjs_1.BehaviorSubject(initialState) }));
}
exports.eventReducer = eventReducer;
/**
* Creates a function that once applied to a stream of events will reduce state and events over time
* similar to rxjs ``scan`` operator but will multicast values to subscribers
* @param stateMachine a state machine defined as a dictionary see: ``EventStateMachineStructure``
* @param initialState state that will be reduced will the first event or state at time 0
* @returns operator function to reduce state and events over time
*/
function eventStateMachine(stateMachine, initialState) {
return eventReducer(createStateMachineReducer(stateMachine), initialState);
}
exports.eventStateMachine = eventStateMachine;
//# sourceMappingURL=actor.model.js.map
;