appfuel 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +25 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +19 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/appfuel.gemspec +42 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/appfuel.rb +210 -0
- data/lib/appfuel/application.rb +4 -0
- data/lib/appfuel/application/app_container.rb +223 -0
- data/lib/appfuel/application/container_class_registration.rb +22 -0
- data/lib/appfuel/application/container_key.rb +201 -0
- data/lib/appfuel/application/qualify_container_key.rb +76 -0
- data/lib/appfuel/application/root.rb +140 -0
- data/lib/appfuel/cli_msg_request.rb +19 -0
- data/lib/appfuel/configuration.rb +14 -0
- data/lib/appfuel/configuration/definition_dsl.rb +175 -0
- data/lib/appfuel/configuration/file_loader.rb +61 -0
- data/lib/appfuel/configuration/populate.rb +95 -0
- data/lib/appfuel/configuration/search.rb +45 -0
- data/lib/appfuel/db_model.rb +16 -0
- data/lib/appfuel/domain.rb +7 -0
- data/lib/appfuel/domain/criteria.rb +436 -0
- data/lib/appfuel/domain/domain_name_parser.rb +44 -0
- data/lib/appfuel/domain/dsl.rb +247 -0
- data/lib/appfuel/domain/entity.rb +242 -0
- data/lib/appfuel/domain/entity_collection.rb +87 -0
- data/lib/appfuel/domain/expr.rb +127 -0
- data/lib/appfuel/domain/value_object.rb +7 -0
- data/lib/appfuel/errors.rb +104 -0
- data/lib/appfuel/feature.rb +2 -0
- data/lib/appfuel/feature/action_loader.rb +25 -0
- data/lib/appfuel/feature/initializer.rb +43 -0
- data/lib/appfuel/handler.rb +6 -0
- data/lib/appfuel/handler/action.rb +17 -0
- data/lib/appfuel/handler/base.rb +103 -0
- data/lib/appfuel/handler/command.rb +18 -0
- data/lib/appfuel/handler/inject_dsl.rb +88 -0
- data/lib/appfuel/handler/validator_dsl.rb +256 -0
- data/lib/appfuel/initialize.rb +70 -0
- data/lib/appfuel/initialize/initializer.rb +68 -0
- data/lib/appfuel/msg_request.rb +207 -0
- data/lib/appfuel/predicates.rb +10 -0
- data/lib/appfuel/presenter.rb +18 -0
- data/lib/appfuel/presenter/base.rb +7 -0
- data/lib/appfuel/repository.rb +73 -0
- data/lib/appfuel/repository/base.rb +72 -0
- data/lib/appfuel/repository/initializer.rb +19 -0
- data/lib/appfuel/repository/mapper.rb +203 -0
- data/lib/appfuel/repository/mapping_dsl.rb +210 -0
- data/lib/appfuel/repository/mapping_entry.rb +76 -0
- data/lib/appfuel/repository/mapping_registry.rb +121 -0
- data/lib/appfuel/repository_runner.rb +60 -0
- data/lib/appfuel/request.rb +53 -0
- data/lib/appfuel/response.rb +96 -0
- data/lib/appfuel/response_handler.rb +79 -0
- data/lib/appfuel/root_module.rb +31 -0
- data/lib/appfuel/run_error.rb +9 -0
- data/lib/appfuel/storage.rb +3 -0
- data/lib/appfuel/storage/db.rb +4 -0
- data/lib/appfuel/storage/db/active_record_model.rb +42 -0
- data/lib/appfuel/storage/db/mapper.rb +213 -0
- data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
- data/lib/appfuel/storage/db/migration_runner.rb +15 -0
- data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
- data/lib/appfuel/storage/db/repository.rb +231 -0
- data/lib/appfuel/storage/db/repository_query.rb +13 -0
- data/lib/appfuel/storage/file.rb +1 -0
- data/lib/appfuel/storage/file/base.rb +32 -0
- data/lib/appfuel/storage/memory.rb +2 -0
- data/lib/appfuel/storage/memory/mapper.rb +30 -0
- data/lib/appfuel/storage/memory/repository.rb +37 -0
- data/lib/appfuel/types.rb +53 -0
- data/lib/appfuel/validation.rb +80 -0
- data/lib/appfuel/validation/validator.rb +59 -0
- data/lib/appfuel/validation/validator_pipe.rb +47 -0
- data/lib/appfuel/version.rb +3 -0
- metadata +335 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Domain
|
3
|
+
# Currently this only answers the use case where a collection of active
|
4
|
+
# record models are converted into a collection of domain entities via
|
5
|
+
# a entity loader.
|
6
|
+
#
|
7
|
+
# NOTE: There is no ability yet to add or track the entity state
|
8
|
+
#
|
9
|
+
class EntityCollection
|
10
|
+
include Enumerable
|
11
|
+
attr_reader :domain_name, :domain_basename, :entity_loader
|
12
|
+
|
13
|
+
def initialize(domain_name, entity_loader = nil)
|
14
|
+
unless Types.key?(domain_name)
|
15
|
+
fail "#{domain_name} is not a registered type"
|
16
|
+
end
|
17
|
+
|
18
|
+
@pager = nil
|
19
|
+
@list = []
|
20
|
+
@loaded = false
|
21
|
+
|
22
|
+
parts = domain_name.split('.')
|
23
|
+
@domain_name = domain_name
|
24
|
+
@domain_basename = parts.last
|
25
|
+
@is_global = parts.size == 1
|
26
|
+
|
27
|
+
self.entity_loader = entity_loader if entity_loader
|
28
|
+
end
|
29
|
+
|
30
|
+
def collection?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def global?
|
35
|
+
@is_global
|
36
|
+
end
|
37
|
+
|
38
|
+
def all
|
39
|
+
load_entities
|
40
|
+
@list
|
41
|
+
end
|
42
|
+
|
43
|
+
def first
|
44
|
+
load_entities
|
45
|
+
@list.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
load_entities
|
50
|
+
return @list.each unless block_given?
|
51
|
+
|
52
|
+
@list.each {|entity| yield entity}
|
53
|
+
end
|
54
|
+
|
55
|
+
def pager
|
56
|
+
load_entities
|
57
|
+
@pager
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_a
|
61
|
+
list = []
|
62
|
+
each do |entity|
|
63
|
+
list << entity.to_h
|
64
|
+
end
|
65
|
+
list
|
66
|
+
end
|
67
|
+
|
68
|
+
def entity_loader?
|
69
|
+
!@entity_loader.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def entity_loader=(loader)
|
73
|
+
fail "Entity loader must implement call" unless loader.respond_to?(:call)
|
74
|
+
@entity_loader = loader
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def load_entities
|
80
|
+
return false if @loaded || !entity_loader?
|
81
|
+
data = entity_loader.call
|
82
|
+
@list = data[:list]
|
83
|
+
@pager = data[:pager]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Domain
|
3
|
+
# Domain expressions are used mostly by the criteria to describe filter
|
4
|
+
# conditions. The class represents a basic expression like "id eq 6", the
|
5
|
+
# problem with this expression is that we need additional information in
|
6
|
+
# order to properly map it to something like a db expression. This call
|
7
|
+
# ensures that additional information exists. Most importantly we need
|
8
|
+
# a fully qualified domain name in the form of "feature.domain".
|
9
|
+
class Expr
|
10
|
+
include DomainNameParser
|
11
|
+
OPS = {
|
12
|
+
eq: '=',
|
13
|
+
gt: '>',
|
14
|
+
gteq: '>=',
|
15
|
+
lt: '<',
|
16
|
+
lteq: '<=',
|
17
|
+
in: 'IN',
|
18
|
+
like: 'LIKE',
|
19
|
+
ilike: 'ILIKE',
|
20
|
+
between: 'BETWEEN'
|
21
|
+
}
|
22
|
+
attr_reader :feature, :domain_basename, :domain_name, :domain_attr, :value
|
23
|
+
|
24
|
+
# Assign the fully qualified domain name, its basename and its attribute
|
25
|
+
# along with the operator and value. Operator and value are assumed to
|
26
|
+
# be the first key => value pair of the hash.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# feature domain
|
30
|
+
# Expr.new('foo.bar', 'id', eq: 6)
|
31
|
+
#
|
32
|
+
# or
|
33
|
+
# global domain
|
34
|
+
# Expr.new('bar', 'name', like: '%Bob%')
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# @param domain [String] fully qualified domain name
|
38
|
+
# @param domain_attr [String, Symbol] attribute name
|
39
|
+
# @param data [Hash] holds operator and value
|
40
|
+
# @option data [Symbol] the key is the operator and value is the value
|
41
|
+
#
|
42
|
+
# @return [Expr]
|
43
|
+
def initialize(domain, domain_attr, data)
|
44
|
+
fail "operator value pair must exist in a hash" unless data.is_a?(Hash)
|
45
|
+
@feature, @domain_basename, @domain_name = parse_domain_name(domain)
|
46
|
+
|
47
|
+
operator, value = data.first
|
48
|
+
@domain_attr = domain_attr.to_s
|
49
|
+
self.op = operator
|
50
|
+
self.value = value
|
51
|
+
|
52
|
+
fail "domain name can not be empty" if @domain_name.empty?
|
53
|
+
fail "domain attribute can not be empty" if @domain_attr.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def feature?
|
57
|
+
!@feature.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def global?
|
61
|
+
!feature?
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Bool]
|
65
|
+
def negated?
|
66
|
+
@negated
|
67
|
+
end
|
68
|
+
|
69
|
+
def expr_string
|
70
|
+
data = yield domain_name, domain_attr, OPS[op]
|
71
|
+
lvalue = data[0]
|
72
|
+
operator = data[1]
|
73
|
+
rvalue = data[2]
|
74
|
+
|
75
|
+
operator = "NOT #{operator}" if negated?
|
76
|
+
"#{lvalue} #{operator} #{rvalue}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
"#{domain_name}.#{domain_attr} #{OPS[op]} #{value}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def op
|
84
|
+
negated? ? "not_#{@op}".to_sym : @op
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def op=(value)
|
90
|
+
negated, value = value.to_s.split('_')
|
91
|
+
@negated = false
|
92
|
+
if negated == 'not'
|
93
|
+
@negated = true
|
94
|
+
else
|
95
|
+
value = negated
|
96
|
+
end
|
97
|
+
value = value.to_sym
|
98
|
+
unless supported_op?(value)
|
99
|
+
fail "op has to be one of [#{OPS.keys.join(',')}]"
|
100
|
+
end
|
101
|
+
@op = value
|
102
|
+
end
|
103
|
+
|
104
|
+
def value=(data)
|
105
|
+
case op
|
106
|
+
when :in
|
107
|
+
unless data.is_a?(Array)
|
108
|
+
fail ":in operator must have an array as a value"
|
109
|
+
end
|
110
|
+
when :range
|
111
|
+
unless data.is_a?(Range)
|
112
|
+
fail ":range operator must have a range as a value"
|
113
|
+
end
|
114
|
+
when :gt, :gteq, :lt, :lteq
|
115
|
+
unless data.is_a?(Numeric)
|
116
|
+
fail ":gt, :gteq, :lt, :lteq operators expect a numeric value"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
@value = data
|
120
|
+
end
|
121
|
+
|
122
|
+
def supported_op?(op)
|
123
|
+
OPS.keys.include?(op)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Appfuel
|
2
|
+
# Feature handler, action handler, command handler all use this class.
|
3
|
+
# Presenters and validators will have there errors tranformed into this.
|
4
|
+
# Errors are a basic hash structure where each key has an array of strings
|
5
|
+
# that represent error messages.
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
# messages: {
|
9
|
+
# name: [
|
10
|
+
# 'must be present',
|
11
|
+
# 'can not be blank',
|
12
|
+
# 'can not be Bob'
|
13
|
+
# ]
|
14
|
+
# }
|
15
|
+
class Errors
|
16
|
+
include Enumerable
|
17
|
+
attr_reader :messages
|
18
|
+
|
19
|
+
def initialize(messages = {})
|
20
|
+
@messages = messages || {}
|
21
|
+
@messages.stringify_keys! unless @messages.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Defined to use Enumerable so that we can treat errors
|
25
|
+
# as an iterator
|
26
|
+
def each
|
27
|
+
messages.each do|key, msgs|
|
28
|
+
yield key, msgs
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add an error message to a given key
|
33
|
+
#
|
34
|
+
# @param key Symbol key for this message
|
35
|
+
# @param msg String the message to be stored
|
36
|
+
def add(key, msg)
|
37
|
+
key = key.to_s
|
38
|
+
msg = msg.to_s
|
39
|
+
messages[key] = [] unless messages.key?(key)
|
40
|
+
messages[key] << msg unless messages[key].include?(msg)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Formats the list of messages for each key
|
44
|
+
#
|
45
|
+
# Example
|
46
|
+
# messages: {
|
47
|
+
# name: [
|
48
|
+
# ' must be present ',
|
49
|
+
# ' can not be blank ',
|
50
|
+
# ' can not be Bob '
|
51
|
+
# ]
|
52
|
+
# }
|
53
|
+
#
|
54
|
+
# note: spaces are used only for readability
|
55
|
+
# name: must be present \n can not be blank \n can not be Bob \n \n
|
56
|
+
#
|
57
|
+
# @param msg_separator String separates each message default \n
|
58
|
+
# @param list_separator String separates each list of messages
|
59
|
+
# @return String
|
60
|
+
def format(msg_separator = "\n", list_separator = "\n")
|
61
|
+
msg = ''
|
62
|
+
each do |key, list|
|
63
|
+
msg << "#{key}: #{list.join(msg_separator)}#{list_separator}"
|
64
|
+
end
|
65
|
+
msg
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete(key)
|
69
|
+
messages.delete(key.to_s)
|
70
|
+
end
|
71
|
+
|
72
|
+
def [](key)
|
73
|
+
messages[key.to_s]
|
74
|
+
end
|
75
|
+
|
76
|
+
def size
|
77
|
+
messages.length
|
78
|
+
end
|
79
|
+
|
80
|
+
def values
|
81
|
+
messages.values
|
82
|
+
end
|
83
|
+
|
84
|
+
def keys
|
85
|
+
messages.keys
|
86
|
+
end
|
87
|
+
|
88
|
+
def clear
|
89
|
+
messages.clear
|
90
|
+
end
|
91
|
+
|
92
|
+
def empty?
|
93
|
+
messages.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_h
|
97
|
+
{errors: messages}
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
format
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Feature
|
3
|
+
# Loads an action from the container using its fully qualified namespace.
|
4
|
+
# This class has been abstracted out because its Appfuel's implementation
|
5
|
+
# of loading an action. This action loader is injected into the container
|
6
|
+
# during setup which allows the client to use their own if this basic
|
7
|
+
# lookup mehtod does not work for them.
|
8
|
+
#
|
9
|
+
# The idea is that all actions, commands and repositories auto register
|
10
|
+
# themselves into the container based on a namespace derived inpart by
|
11
|
+
# their own ruby namespace.
|
12
|
+
class ActionLoader
|
13
|
+
# @raises RuntimeError when key is not found
|
14
|
+
# @param namespace [String] fully qualifed container namespace
|
15
|
+
# @param container [Dry::Container] application container
|
16
|
+
# @return [Appfuel::Handler::Action]
|
17
|
+
def call(namespace, container)
|
18
|
+
unless container.key?(namespace)
|
19
|
+
fail "[ActionLoader] Could not load action at #{namespace}"
|
20
|
+
end
|
21
|
+
container[namespace]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Feature
|
3
|
+
# Run a given feature's initializers. Each feature can declare any number
|
4
|
+
# of initializers just as the application does. This allow dependencies
|
5
|
+
# and vendor code to be initialized only when the feature is used.
|
6
|
+
class Initializer
|
7
|
+
# Ensure the correct namespaces are registered so that the initializer
|
8
|
+
# dsl will work and then require the feature and run its intializers
|
9
|
+
# unless instructed not too. Initializers are only run once.
|
10
|
+
#
|
11
|
+
# @param name [String] name of the feature as found in the container
|
12
|
+
# @param container [Dry::Container] application container
|
13
|
+
# @return [Boolean]
|
14
|
+
def call(name, container)
|
15
|
+
name = name.to_s.underscore
|
16
|
+
feature_key = "features.#{name}"
|
17
|
+
unless container.key?(feature_key)
|
18
|
+
Appfuel.setup_container_dependencies(feature_key, container)
|
19
|
+
end
|
20
|
+
|
21
|
+
unless require_feature_disabled?(container, feature_key)
|
22
|
+
require "#{container[:features_path]}/#{name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
return false if initialized?(container, feature_key)
|
26
|
+
|
27
|
+
Appfuel.run_initializers(feature_key, container)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def require_feature_disabled?(container, feature_key)
|
33
|
+
disable_key = "#{feature_key}.disable_require"
|
34
|
+
container.key?(disable_key) && container[disable_key] == true
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialized?(container, feature_key)
|
38
|
+
init_key = "#{feature_key}.initialized"
|
39
|
+
container.key?(init_key) && container[init_key] == true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Handler
|
3
|
+
class Action < Base
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# In order to reduce the length of namespaces actions are not required
|
7
|
+
# to be inside an Actions namespace, but, it is namespaced with in the
|
8
|
+
# application container, so we adjust for that here.
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
def container_relative_key
|
12
|
+
"actions.#{super}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Handler
|
3
|
+
class Base
|
4
|
+
extend ValidatorDsl
|
5
|
+
extend InjectDsl
|
6
|
+
include Appfuel::Application::AppContainer
|
7
|
+
|
8
|
+
# Class level interfaces used by the framwork to register and run
|
9
|
+
# handlers
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Register the extending class with the application container
|
13
|
+
#
|
14
|
+
# @param klass [Class] the handler class that is inheriting this
|
15
|
+
# @return nil
|
16
|
+
def inherited(klass)
|
17
|
+
register_container_class(klass)
|
18
|
+
end
|
19
|
+
|
20
|
+
def response_handler
|
21
|
+
@response_handler ||= ResponseHandler.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Run will validate all inputs; returning on input failures, resolving
|
25
|
+
# declared dependencies, then delegate to the handlers call method with
|
26
|
+
# its valid inputs and resolved dependencies. Finally it ensure every
|
27
|
+
# response is a Response object.
|
28
|
+
#
|
29
|
+
# @param inputs [Hash] inputs to be validated
|
30
|
+
# @return [Response]
|
31
|
+
def run(inputs = {}, container = Dry::Container.new)
|
32
|
+
begin
|
33
|
+
response = resolve_inputs(inputs)
|
34
|
+
return response if response.failure?
|
35
|
+
valid_inputs = response.ok
|
36
|
+
|
37
|
+
resolve_dependencies(container)
|
38
|
+
handler = self.new(container)
|
39
|
+
result = handler.call(valid_inputs)
|
40
|
+
result = create_response(result) unless response?(result)
|
41
|
+
rescue RunError => e
|
42
|
+
result = e.response
|
43
|
+
rescue StandardError => e
|
44
|
+
result = error(e)
|
45
|
+
end
|
46
|
+
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def error(*args)
|
51
|
+
response_handler.error(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ok(value = nil)
|
55
|
+
response_handler.ok(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def response?(value)
|
59
|
+
response_handler.response?(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_response(data)
|
63
|
+
response_handler.create_response(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_reader :data
|
69
|
+
|
70
|
+
def initialize(container = Dry::Container.new)
|
71
|
+
@data = container
|
72
|
+
end
|
73
|
+
|
74
|
+
def call(inputs, data = {})
|
75
|
+
fail "Concrete handlers must implement their own call"
|
76
|
+
end
|
77
|
+
|
78
|
+
def ok(value = nil)
|
79
|
+
self.class.ok(value)
|
80
|
+
end
|
81
|
+
|
82
|
+
def error(*args)
|
83
|
+
self.class.error(*args)
|
84
|
+
end
|
85
|
+
|
86
|
+
def present(name, data, inputs = {})
|
87
|
+
return data if inputs[:raw] == true
|
88
|
+
|
89
|
+
key = qualify_container_key(name, 'presenters')
|
90
|
+
container = self.class.app_container
|
91
|
+
unless container.key?(key)
|
92
|
+
unless data.respond_to?(:to_h)
|
93
|
+
fail "data must implement :to_h for generic presentation"
|
94
|
+
end
|
95
|
+
|
96
|
+
return data.to_h
|
97
|
+
end
|
98
|
+
|
99
|
+
container[key].call(data, inputs)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|