<commit_before>eventric = require 'eventric'

_                  = eventric.require 'HelperUnderscore'
AggregateService   = eventric.require 'AggregateService'
DomainEventService = eventric.require 'DomainEventService'


class BoundedContext

  constructor: (@name) ->
    @_di = {}
    @_params = {}
    @_aggregateRootClasses = {}
    @_adapterClasses = {}
    @_adapterInstances = {}
    @_commandHandlers = {}
    @_domainEventClasses = {}
    @_domainEventHandlers = {}
    @_readModelClasses = {}
    @_readModelInstances = {}


  initialize: ->
    @_initializeStore()
    @_initializeReadModels()
    @_initializeAdapters()

    @_domainEventService = new DomainEventService
    @_domainEventService.initialize @_store, @
    @_initializeDomainEventHandlers()

    @_aggregateService = new AggregateService
    @_aggregateService.initialize @_store, @_domainEventService, @
    @_initializeAggregateService()

    @_di =
      $aggregate: @_aggregateService
      $adapter: => @getAdapter.apply @, arguments
      $readmodel: => @getReadModel.apply @, arguments
    @


  _initializeReadModels: ->
    for readModelName, ReadModelClass of @_readModelClasses
      @_initializeReadModel readModelName, ReadModelClass


  _initializeReadModel: (readModelName, ReadModelClass) ->
    @_store.collection "#{@name}.ReadModel.#{readModelName}", (err, collection) =>
      readModel = new ReadModelClass
      # TODO: change the injected variable name to "$mongodb, $mysql etc" (@_store.name)
      readModel.$store = collection
      readModel.$adapter = => @getAdapter.apply @, arguments
      if readModel.subscribeToDomainEvents
        for eventName in readModel.subscribeToDomainEvents
          @_subscribeReadModelToDomainEvent readModel, eventName

      @_readModelInstances[readModelName] = readModel


  _subscribeReadModelToDomainEvent: (readModel, eventName) ->
    @addDomainEventHandler eventName, (domainEvent) =>
      @_applyDomainEventToReadModel domainEvent, readModel


  _applyDomainEventToReadModel: (domainEvent, readModel) ->
    if !readModel["handle#{domainEvent.name}"]
      throw new Error "Tried to apply DomainEvent '#{domainEvent.name}' to ReadModel without a matching handle method"

    else
      readModel["handle#{domainEvent.name}"] domainEvent


  _initializeAdapters: ->
    for adapterName, adapterClass of @_adapterClasses
      adapter = new @_adapterClasses[adapterName]
      adapter.initialize?()

      @_adapterInstances[adapterName] = adapter


  _initializeStore: ->
    if @_params.store
      @_store = @_params.store
    else
      globalStore = eventric.get 'store'
      if globalStore
        @_store = globalStore
      else
        throw new Error 'Missing Event Store for Bounded Context'


  _initializeDomainEventHandlers: ->
    for domainEventName, fnArray of @_domainEventHandlers
      for fn in fnArray
        @_domainEventService.on domainEventName, fn


  _initializeAggregateService: ->
    for aggregateName, AggregateRoot of @_aggregateRootClasses
      @_aggregateService.registerAggregateRoot aggregateName, AggregateRoot


  set: (key, value) ->
    @_params[key] = value
    @


  addDomainEvent: (domainEventName, DomainEventClass) ->
    @_domainEventClasses[domainEventName] = DomainEventClass
    @


  addDomainEvents: (domainEventClassesObj) ->
    @addDomainEvent domainEventName, DomainEventClass for domainEventName, DomainEventClass of domainEventClassesObj
    @


  addCommandHandler: (commandHandlerName, commandHandlerFn) ->
    @_commandHandlers[commandHandlerName] = => commandHandlerFn.apply @_di, arguments
    @


  addCommandHandlers: (commandObj) ->
    @addCommandHandler commandHandlerName, commandFunction for commandHandlerName, commandFunction of commandObj
    @


  addAggregate: (aggregateName, AggregateRootClass) ->
    @_aggregateRootClasses[aggregateName] = AggregateRootClass
    @


  addDomainEventHandler: (eventName, handlerFn) ->
    @_domainEventHandlers[eventName] = [] unless @_domainEventHandlers[eventName]
    @_domainEventHandlers[eventName].push => handlerFn.apply @_di, arguments
    @


  addDomainEventHandlers: (domainEventHandlersObj) ->
    @addDomainEventHandler eventName, handlerFn for eventName, handlerFn of domainEventHandlersObj
    @


  addAdapter: (adapterName, adapterClass) ->
    @_adapterClasses[adapterName] = adapterClass
    @


  addAdapters: (adapterObj) ->
    @addAdapter adapterName, fn for adapterName, fn of adapterObj
    @


  addReadModel: (readModelName, ReadModelClass) ->
    @_readModelClasses[readModelName] = ReadModelClass
    @


  addReadModels: (viewsObj) ->
    @addReadModel readModelName, ReadModelClass for readModelName, ReadModelClass of viewsObj
    @


  getReadModel: (readModelName) ->
    @_readModelInstances[readModelName]


  getAdapter: (adapterName) ->
    @_adapterInstances[adapterName]


  getDomainEvent: (domainEventName) ->
    @_domainEventClasses[domainEventName]


  command: (command, callback) ->
    new Promise (resolve, reject) =>
      if @_commandHandlers[command.name]
        @_commandHandlers[command.name] command.params, (err, result) =><commit_msg>Migrate API docs from github wiki to source (based on dgeni)<commit_after>eventric = require 'eventric'

_                  = eventric.require 'HelperUnderscore'
AggregateService   = eventric.require 'AggregateService'
DomainEventService = eventric.require 'DomainEventService'


class BoundedContext

  constructor: (@name) ->
    @_di = {}
    @_params = {}
    @_aggregateRootClasses = {}
    @_adapterClasses = {}
    @_adapterInstances = {}
    @_commandHandlers = {}
    @_domainEventClasses = {}
    @_domainEventHandlers = {}
    @_readModelClasses = {}
    @_readModelInstances = {}



  ###*
  * @name initialize
  *
  * @description
  * Use as: initialize(callback)
  *
  * Initializes the `BoundedContext` after the `add*` Methods
  *
  * @example
    ```javascript
    exampleContext.initialize(function() {
      // ...
    })
    ```
  ###
  initialize: ->
    @_initializeStore()
    @_initializeReadModels()
    @_initializeAdapters()

    @_domainEventService = new DomainEventService
    @_domainEventService.initialize @_store, @
    @_initializeDomainEventHandlers()

    @_aggregateService = new AggregateService
    @_aggregateService.initialize @_store, @_domainEventService, @
    @_initializeAggregateService()

    @_di =
      $aggregate: @_aggregateService
      $adapter: => @getAdapter.apply @, arguments
      $readmodel: => @getReadModel.apply @, arguments
    @


  _initializeReadModels: ->
    for readModelName, ReadModelClass of @_readModelClasses
      @_initializeReadModel readModelName, ReadModelClass


  _initializeReadModel: (readModelName, ReadModelClass) ->
    @_store.collection "#{@name}.ReadModel.#{readModelName}", (err, collection) =>
      readModel = new ReadModelClass
      # TODO: change the injected variable name to "$mongodb, $mysql etc" (@_store.name)
      readModel.$store = collection
      readModel.$adapter = => @getAdapter.apply @, arguments
      if readModel.subscribeToDomainEvents
        for eventName in readModel.subscribeToDomainEvents
          @_subscribeReadModelToDomainEvent readModel, eventName

      @_readModelInstances[readModelName] = readModel


  _subscribeReadModelToDomainEvent: (readModel, eventName) ->
    @addDomainEventHandler eventName, (domainEvent) =>
      @_applyDomainEventToReadModel domainEvent, readModel


  _applyDomainEventToReadModel: (domainEvent, readModel) ->
    if !readModel["handle#{domainEvent.name}"]
      throw new Error "Tried to apply DomainEvent '#{domainEvent.name}' to ReadModel without a matching handle method"

    else
      readModel["handle#{domainEvent.name}"] domainEvent


  _initializeAdapters: ->
    for adapterName, adapterClass of @_adapterClasses
      adapter = new @_adapterClasses[adapterName]
      adapter.initialize?()

      @_adapterInstances[adapterName] = adapter


  _initializeStore: ->
    if @_params.store
      @_store = @_params.store
    else
      globalStore = eventric.get 'store'
      if globalStore
        @_store = globalStore
      else
        throw new Error 'Missing Event Store for Bounded Context'


  _initializeDomainEventHandlers: ->
    for domainEventName, fnArray of @_domainEventHandlers
      for fn in fnArray
        @_domainEventService.on domainEventName, fn


  _initializeAggregateService: ->
    for aggregateName, AggregateRoot of @_aggregateRootClasses
      @_aggregateService.registerAggregateRoot aggregateName, AggregateRoot


  ###*
  * @name set
  *
  * @description
  * > Use as: set(key, value)
  * Configure settings for the `BoundedContext`.
  *
  * @example

     exampleContext.set 'store', StoreAdapter

  *
  * @param {Object} key
  * Available keys are: `store` Eventric Store Adapter
  ###
  set: (key, value) ->
    @_params[key] = value
    @


  addDomainEvent: (domainEventName, DomainEventClass) ->
    @_domainEventClasses[domainEventName] = DomainEventClass
    @


  addDomainEvents: (domainEventClassesObj) ->
    @addDomainEvent domainEventName, DomainEventClass for domainEventName, DomainEventClass of domainEventClassesObj
    @


  ###*
  * @name addCommandHandler
  *
  * @dscription
  * Use as: addCommandHandler(commandName, commandFunction)
  *
  * Add Commands to the `BoundedContext`. These will be available to the `command` method after calling `initialize`.
  *
  * @example
    ```javascript
    exampleContext.addCommandHandler('someCommand', function(params, callback) {
      // ...
    });
    ```

  * @param {String} commandName Name of the command
  *
  * @param {String} commandFunction Gets `this.aggregate` dependency injected
  * `this.aggregate.command(params)` Execute command on Aggregate
  *  * `params.name` Name of the Aggregate
  *  * `params.id` Id of the Aggregate
  *  * `params.methodName` MethodName inside the Aggregate
  *  * `params.methodParams` Array of params which the specified AggregateMethod will get as function signature using a [splat](http://stackoverflow.com/questions/6201657/what-does-splats-mean-in-the-coffeescript-tutorial)
  *
  * `this.aggregate.create(params)` Execute command on Aggregate
  *  * `params.name` Name of the Aggregate to be created
  *  * `params.props` Initial properties so be set on the Aggregate or handed to the Aggregates create() method
  ###
  addCommandHandler: (commandHandlerName, commandHandlerFn) ->
    @_commandHandlers[commandHandlerName] = => commandHandlerFn.apply @_di, arguments
    @


  addCommandHandlers: (commandObj) ->
    @addCommandHandler commandHandlerName, commandFunction for commandHandlerName, commandFunction of commandObj
    @


  ###*
  * @name addAggregate
  *
  * @description
  *
  * Use as: addAggregate(aggregateName, aggregateDefinition)
  *
  * Add [Aggregates](https://github.com/efacilitation/eventric/wiki/BuildingBlocks#aggregateroot) to the `BoundedContext`. It takes an AggregateDefinition as argument. The AggregateDefinition must at least consists of one AggregateRoot and can optionally have multiple named AggregateEntities. The Root and Entities itself are completely vanilla since eventric follows the philosophy that your DomainModel-Code should be technology-agnostic.
  *
  * @example

  ```javascript
  exampleContext.addAggregate('Example', {
    root: function(){
      this.doSomething = function(description) {
        // ...
      }
    },
    entities: {
      'ExampleEntityOne': function() {},
      'ExampleEntityTwo': function() {}
    }
  });
  ```
  *
  * @param {String} aggregateName Name of the Aggregate
  * @param {String} aggregateDefinition Definition containing root and entities
  ###
  addAggregate: (aggregateName, AggregateRootClass) ->
    @_aggregateRootClasses[aggregateName] = AggregateRootClass
    @


  ###*
  *
  * @name addDomainEventHandler
  *
  * @description
  * Use as: addDomainEventHandler(domainEventName, domainEventHandlerFunction)
  *
  * Add handler function which gets called when a specific `DomainEvent` gets triggered
  *
  * @example
    ```javascript
    exampleContext.addDomainEventHandler('Example:create', function(domainEvent) {
      // ...
    });
    ```
  *
  * @param {String} domainEventName Name of the `DomainEvent`
  *
  * @param {Function} Function which gets called with `domainEvent` as argument
  * - `domainEvent` Instance of [[DomainEvent]]
  ###
  addDomainEventHandler: (eventName, handlerFn) ->
    @_domainEventHandlers[eventName] = [] unless @_domainEventHandlers[eventName]
    @_domainEventHandlers[eventName].push => handlerFn.apply @_di, arguments
    @


  addDomainEventHandlers: (domainEventHandlersObj) ->
    @addDomainEventHandler eventName, handlerFn for eventName, handlerFn of domainEventHandlersObj
    @


  addAdapter: (adapterName, adapterClass) ->
    @_adapterClasses[adapterName] = adapterClass
    @


  addAdapters: (adapterObj) ->
    @addAdapter adapterName, fn for adapterName, fn of adapterObj
    @


  addReadModel: (readModelName, ReadModelClass) ->
    @_readModelClasses[readModelName] = ReadModelClass
    @


  addReadModels: (viewsObj) ->
    @addReadModel readModelName, ReadModelClass for readModelName, ReadModelClass of viewsObj
    @


  getReadModel: (readModelName) ->
    @_readModelInstances[readModelName]


  getAdapter: (adapterName) ->
    @_adapterInstances[adapterName]


  getDomainEvent: (domainEventName) ->
    @_domainEventClasses[domainEventName]


  ###*
  * @name command
  *
  * @description
  * Use as: command(command, callback)
  *
  * Execute previously added `commands`. Upon successful execution this will trigger a `DomainEvent`.
  *
  * @example
    ```javascript
    exampleContext.command({
      name: 'doSomething'
    },
    function(err, result) {
      // callback
    });
    ```
  *
  * @param {Object} command Object containing the command definition
  * - `name` The name of the `command`
  * - `params` Object containing parameters. The `command` will get this as first parameter.
  *
  * @param {Function} callback Gets called after the command got executed with the arguments:
  * - `err` null if successful
  * - `result` Set by the `command`
  ###
  command: (command, callback) ->
    new Promise (resolve, reject) =>
      if @_commandHandlers[command.name]
        @_commandHandlers[command.name] command.params, (err, result) =>
{'fraction_non_alphanumeric': 0.09814544696811836, 'fraction_numerical': 0.00048621240536222824, 'mean_word_length': 3.8641891891891893, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 74, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5294408', 'n_tokens_mistral': 4254, 'n_tokens_neox': 4099, 'n_words': 958}