UNPKG

@fraktalio/fmodel-ts

Version:

Functional domain modeling with TypeScript. Optimized for event sourcing and CQRS

127 lines 9.72 kB
/* * 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. */ /** * `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` */ export 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); } } /** * `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` */ export 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; } } /** * 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 */ export 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); } } /** * 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 */ export 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); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGVzdG9yZWQtYWdncmVnYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9hcHBsaWNhdGlvbi9zdGF0ZXN0b3JlZC1hZ2dyZWdhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7O0dBV0c7QUFxRkg7Ozs7OztHQU1HO0FBQ0gsTUFBTSxPQUFnQixnQkFBZ0I7SUFDcEMsWUFBeUMsT0FBMEI7UUFBMUIsWUFBTyxHQUFQLE9BQU8sQ0FBbUI7UUFDakUsSUFBSSxDQUFDLFlBQVksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDO0lBQzNDLENBQUM7SUFDRCxNQUFNLENBQUMsT0FBVSxFQUFFLEtBQVE7UUFDekIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDN0MsQ0FBQztJQUNELE1BQU0sQ0FBQyxLQUFRLEVBQUUsS0FBUTtRQUN2QixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMzQyxDQUFDO0lBR1MsZUFBZSxDQUFDLEtBQVEsRUFBRSxPQUFVO1FBQzVDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNuRCxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDbkQsQ0FBQztDQUNGO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBZ0IsNkJBQ3BCLFNBQVEsZ0JBQXlCO0lBR2pDLFlBQ0UsT0FBMEIsRUFDUCxJQUFpQjtRQUVwQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFGSSxTQUFJLEdBQUosSUFBSSxDQUFhO0lBR3RDLENBQUM7SUFDRCxLQUFLLENBQUMsS0FBUTtRQUNa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