@fraktalio/fmodel-ts
Version:
Functional domain modeling with TypeScript. Optimized for event sourcing and CQRS
134 lines • 10.2 kB
JavaScript
"use strict";
/*
* Copyright 2023 Fraktalio D.O.O. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
* AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StateStoredOrchestratingAggregate = exports.StateStoredAggregate = exports.StateOrchestratingComputation = exports.StateComputation = void 0;
/**
* `StateComputation` abstracts the `State Computation` algorithm by using a `decider` of type `IDecider`<`C`, `S,` `E`> to handle commands based on the current state, and produce new state.
*
* @typeParam C - Commands of type `C`
* @typeParam S - State of type `S`
* @typeParam E - Events of type `E`
*/
class StateComputation {
constructor(decider) {
this.decider = decider;
this.initialState = decider.initialState;
}
decide(command, state) {
return this.decider.decide(command, state);
}
evolve(state, event) {
return this.decider.evolve(state, event);
}
computeNewState(state, command) {
const events = this.decider.decide(command, state);
return events.reduce(this.decider.evolve, state);
}
}
exports.StateComputation = StateComputation;
/**
* `StateOrchestratingComputation` abstracts the `Orchestrating State Computation` algorithm by using a `decider` of type `IDecider`<`C`, `S,` `E`> and `saga` of type `ISaga`<`E`, `C`> to handle commands based on the current state, and produce new state.
* If the `decider` is combined out of many deciders via `combine` function, a `saga` could be used to react on new events and send new commands to the `decider` recursively, in single transaction.
*
* @typeParam C - Commands of type `C`
* @typeParam S - State of type `S`
* @typeParam E - Events of type `E`
*/
class StateOrchestratingComputation extends StateComputation {
constructor(decider, saga) {
super(decider);
this.saga = saga;
}
react(event) {
return this.saga.react(event);
}
computeNewState(state, command) {
const events = this.decider.decide(command, state);
// eslint-disable-next-line functional/no-let
let newState = events.reduce(this.decider.evolve, state);
events
.flatMap((evt) => this.saga.react(evt))
.forEach((cmd) => (newState = this.computeNewState(newState, cmd)));
return newState;
}
}
exports.StateOrchestratingComputation = StateOrchestratingComputation;
/**
* State stored aggregate is using/delegating a `decider` of type `Decider`<`C`, `S`, `E`> to handle commands and produce new state.
* In order to handle the command, aggregate needs to fetch the current state via `IStateRepository.fetchState` function first, and then delegate the command to the `decider` which can produce new state as a result.
*
* New state is then stored via `IStateRepository.save` function.
*
* @typeParam C - Commands of type `C` that this aggregate can handle
* @typeParam S - Aggregate state of type `S`
* @typeParam E - Events of type `E` that this aggregate can publish
* @typeParam V - The Version of the stored State
* @typeParam CM - Command Metadata
* @typeParam SM - State Metadata
*
* @author Иван Дугалић / Ivan Dugalic / @idugalic
*/
class StateStoredAggregate extends StateComputation {
constructor(decider, stateRepository) {
super(decider);
this.stateRepository = stateRepository;
}
async fetch(command) {
return this.stateRepository.fetch(command);
}
async save(state, commandMetadata, version) {
return this.stateRepository.save(state, commandMetadata, version);
}
async handle(command) {
const currentState = await this.stateRepository.fetch(command);
const newState = this.computeNewState(currentState ? currentState : this.decider.initialState, command);
return this.stateRepository.save(newState, command, currentState);
}
}
exports.StateStoredAggregate = StateStoredAggregate;
/**
* State stored orchestrating aggregate is using/delegating a `decider` of type `IDecider`<`C`, `S`, `E`> to handle commands and produce new state.
* In order to handle the command, aggregate needs to fetch the current state via `IStateRepository.fetchState` function first, and then delegate the command to the `decider` which can produce new state as a result.
* If the `decider` is combined out of many deciders via `combine` function, an optional `saga` could be used to react on new events and send new commands to the `decider` recursively, in one transaction.
*
* New state is then stored via `IStateRepository.save` function.
*
* @typeParam C - Commands of type `C` that this aggregate can handle
* @typeParam S - Aggregate state of type `S`
* @typeParam E - Events of type `E` that this aggregate can publish
* @typeParam V - The Version of the stored State
* @typeParam CM - Command Metadata
* @typeParam SM - State Metadata
*
* @author Иван Дугалић / Ivan Dugalic / @idugalic
*/
class StateStoredOrchestratingAggregate extends StateOrchestratingComputation {
constructor(decider, stateRepository, saga) {
super(decider, saga);
this.stateRepository = stateRepository;
}
async fetch(command) {
return this.stateRepository.fetch(command);
}
async save(state, commandMetadata, version) {
return this.stateRepository.save(state, commandMetadata, version);
}
async handle(command) {
const currentState = await this.stateRepository.fetch(command);
const newState = this.computeNewState(currentState ? currentState : this.decider.initialState, command);
return this.stateRepository.save(newState, command, currentState);
}
}
exports.StateStoredOrchestratingAggregate = StateStoredOrchestratingAggregate;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGVzdG9yZWQtYWdncmVnYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9hcHBsaWNhdGlvbi9zdGF0ZXN0b3JlZC1hZ2dyZWdhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7OztHQVdHOzs7QUFxRkg7Ozs7OztHQU1HO0FBQ0gsTUFBc0IsZ0JBQWdCO0lBQ3BDLFlBQXlDLE9BQTBCO1FBQTFCLFlBQU8sR0FBUCxPQUFPLENBQW1CO1FBQ2pFLElBQUksQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQztJQUMzQyxDQUFDO0lBQ0QsTUFBTSxDQUFDLE9BQVUsRUFBRSxLQUFRO1FBQ3pCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFDRCxNQUFNLENBQUMsS0FBUSxFQUFFLEtBQVE7UUFDdkIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUdTLGVBQWUsQ0FBQyxLQUFRLEVBQUUsT0FBVTtRQUM1QyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDbkQsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ25ELENBQUM7Q0FDRjtBQWhCRCw0Q0FnQkM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBc0IsNkJBQ3BCLFNBQVEsZ0JBQXlCO0lBR2pDLFlBQ0UsT0FBMEIsRUFDUCxJQUFpQjtRQUVwQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFGSSxTQUFJLEdBQUosSUFBSSxDQUFhO0lBR3RDLENBQUM7SUFDRCxLQUFLLENBQUMsS0FB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