polyn 0.1.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.
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ module Polyn
21
+ module Serializers
22
+ ##
23
+ # Handles serializing and deserializing data to and from JSON.
24
+ class Json
25
+ def self.serialize!(nats, event, **opts)
26
+ validate_event_instance!(event)
27
+ event = event.to_h
28
+ validate!(nats, event, **opts)
29
+ JSON.generate(event)
30
+ end
31
+
32
+ def self.deserialize!(nats, json, **opts)
33
+ data = deserialize(nats, json, **opts)
34
+ raise data if data.is_a?(Polyn::Errors::Error)
35
+
36
+ data
37
+ end
38
+
39
+ def self.deserialize(nats, json, **opts)
40
+ data = decode(json)
41
+ return data if data.is_a?(Polyn::Errors::Error)
42
+
43
+ error = validate(nats, data, **opts)
44
+ return error if error.is_a?(Polyn::Errors::Error)
45
+
46
+ data = Polyn::Utils::Hash.deep_symbolize_keys(data)
47
+ Event.new(data)
48
+ end
49
+
50
+ def self.decode(json)
51
+ JSON.parse(json)
52
+ rescue JSON::ParserError
53
+ Polyn::Errors::ValidationError.new("Polyn was unable to decode the following message: \n#{json}")
54
+ end
55
+
56
+ def self.validate!(nats, event, **opts)
57
+ result = validate(nats, event, **opts)
58
+ raise result if result.is_a?(Polyn::Errors::Error)
59
+ end
60
+
61
+ def self.validate(nats, event, **opts)
62
+ error = validate_cloud_event(event)
63
+ return error if error.is_a?(Polyn::Errors::Error)
64
+
65
+ validate_data(nats, event, **opts)
66
+ end
67
+
68
+ def self.validate_event_instance!(event)
69
+ if event.instance_of?(Polyn::Event)
70
+ event
71
+ else
72
+ raise Polyn::Errors::ValidationError,
73
+ "Can only serialize `Polyn::Event` instances. got #{event}"
74
+ end
75
+ end
76
+
77
+ def self.validate_cloud_event(event)
78
+ cloud_event_schema = Polyn::CloudEvent.to_h
79
+ validate_schema(cloud_event_schema, event)
80
+ end
81
+
82
+ def self.validate_data(nats, event, **opts)
83
+ type = get_event_type(event)
84
+ return type if type.is_a?(Polyn::Errors::Error)
85
+
86
+ schema = get_schema(nats, type, **opts)
87
+ return schema if schema.is_a?(Polyn::Errors::Error)
88
+
89
+ validate_schema(schema, event)
90
+ end
91
+
92
+ def self.validate_schema(schema, event)
93
+ schema = JSONSchemer.schema(schema)
94
+ errors = schema.validate(event).to_a
95
+ errors = format_schema_errors(errors)
96
+ unless errors.empty?
97
+ return Polyn::Errors::ValidationError.new(combined_error_message(event,
98
+ errors))
99
+ end
100
+
101
+ errors
102
+ end
103
+
104
+ def self.get_event_type(event)
105
+ if event["type"]
106
+ Polyn::Naming.trim_domain_prefix(event["type"])
107
+ else
108
+ Polyn::Errors::ValidationError.new(
109
+ "Could not find a `type` in message #{event.inspect} \nEvery event must have a `type`",
110
+ )
111
+ end
112
+ end
113
+
114
+ def self.get_schema(nats, type, **opts)
115
+ Polyn::SchemaStore.get(nats, type, name: store_name(**opts))
116
+ end
117
+
118
+ def self.format_schema_errors(errors)
119
+ errors.map do |error|
120
+ "Property: `#{error['data_pointer']}` - #{error['type']} - #{error['details']}"
121
+ end
122
+ end
123
+
124
+ def self.combined_error_message(event, errors)
125
+ [
126
+ "Polyn event #{event['id']} from #{event['source']} is not valid",
127
+ "Event data: #{event.inspect}",
128
+ ].concat(errors).join("\n")
129
+ end
130
+
131
+ def self.store_name(**opts)
132
+ opts[:store_name] || Polyn::SchemaStore.store_name
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ module Polyn
21
+ module Utils
22
+ ##
23
+ # Utilities for hash manipulation.
24
+ module Hash
25
+ ##
26
+ # Deep symbolize keys of a hash.
27
+ #
28
+ # @param hash [::Hash] The hash to symbolize.
29
+ #
30
+ # @return [::Hash] The symbolized hash.
31
+ def self.deep_symbolize_keys(hash)
32
+ return hash.map { |item| deep_symbolize_keys(item) } if hash.is_a?(::Array)
33
+ return hash unless hash.is_a?(::Hash)
34
+
35
+ hash.each_with_object({}) do |(key, value), result|
36
+ result[key.to_sym] = deep_symbolize_keys(value)
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Deep stringifies keys
42
+ #
43
+ # @param hash [::Hash] The hash to stringify.
44
+ #
45
+ # @return [::Hash] The stringified hash.
46
+ def self.deep_stringify_keys(hash)
47
+ return hash.map { |item| deep_stringify_keys(item) } if hash.is_a?(::Array)
48
+ return hash unless hash.is_a?(::Hash)
49
+
50
+ hash.each_with_object({}) do |(key, value), result|
51
+ result[key.to_s] = deep_stringify_keys(value)
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Deep camelize keys
57
+ #
58
+ # @param hash [::Hash] The hash to camelize.
59
+ #
60
+ # @return [::Hash] The camelized hash.
61
+ def self.deep_camelize_keys(hash)
62
+ hash.each_with_object({}) do |(key, value), result|
63
+ result[String.to_camel_case(key)] =
64
+ value.is_a?(::Hash) ? deep_camelize_keys(value) : value
65
+ end
66
+ end
67
+
68
+ ##
69
+ # Deep snake cases keys
70
+ #
71
+ # @param hash [::Hash] The hash to snake case.
72
+ #
73
+ # @return [::Hash] The snake cased hash.
74
+ def self.deep_snake_case_keys(hash)
75
+ hash.each_with_object({}) do |(key, value), result|
76
+ result[String.to_snake_case(key.to_s)] =
77
+ value.is_a?(::Hash) ? deep_snake_case_keys(value) : value
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ module Polyn
21
+ module Utils
22
+ ##
23
+ # Utilities for working with strings.
24
+ module String
25
+ ##
26
+ # Converts a string to camel case.
27
+ #
28
+ # @param str ing [String] The string to convert.
29
+ #
30
+ # @return [String] The camel cased string.
31
+ def self.to_camel_case(str)
32
+ str = str.to_s.split("_").map(&:capitalize).join
33
+ str[0] = str[0].downcase
34
+ str
35
+ end
36
+
37
+ def self.to_snake_case(str)
38
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
39
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
40
+ .tr("-", "_")
41
+ .downcase
42
+ end
43
+
44
+ def self.to_class_name(str)
45
+ str.split("_").map(&:capitalize).join
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ require "polyn/utils/hash"
21
+ require "polyn/utils/string"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polyn
4
+ VERSION = "0.1.0"
5
+ end
data/lib/polyn.rb ADDED
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ # Loading all our classes up front
21
+ require "json_schemer"
22
+ require "json"
23
+ require "nats/client"
24
+ require "securerandom"
25
+
26
+ require "polyn/configuration"
27
+ require "polyn/cloud_event"
28
+ require "polyn/errors/errors"
29
+ require "polyn/event"
30
+ require "polyn/naming"
31
+ require "polyn/pull_subscriber"
32
+ require "polyn/schema_store"
33
+ require "polyn/serializers/json"
34
+ require "polyn/utils/utils"
35
+ require "polyn/version"
36
+
37
+ ##
38
+ # Polyn is a Reactive service framework.
39
+ module Polyn
40
+ ##
41
+ # Publishes a message on the Polyn network.
42
+ #
43
+ # @param nats [Object] Connected NATS instance from `NATS.connect`
44
+ # @param type [String] The type of event
45
+ # @param data [any] The data to include in the event
46
+ # @option options [String] :source - information to specify the source of the event
47
+ # @option options [String] :triggered_by - The event that triggered this one.
48
+ # Will use information from the event to build up the `polyntrace` data
49
+ # @option options [String] :reply_to - Reply to a specific topic
50
+ # @option options [String] :header - Headers to include in the message
51
+ def self.publish(nats, type, data, **opts)
52
+ event = Event.new({
53
+ type: type,
54
+ source: opts[:source],
55
+ data: data,
56
+ triggered_by: opts[:triggered_by],
57
+ })
58
+
59
+ json = Polyn::Serializers::Json.serialize!(nats, event, **opts)
60
+
61
+ nats.publish(type, json, opts[:reply_to], header: opts[:header])
62
+ end
63
+
64
+ ## Create subscription which is dispatched asynchronously
65
+ # and sends messages to a callback.
66
+ #
67
+ # @param nats [Object] Connected NATS instance from `NATS.connect`
68
+ # @param type [String] The type of event
69
+ # @option options [String] :queue - Queue group to add subscriber to
70
+ # @option options [String] :max - Max msgs before unsubscribing
71
+ # @option options [String] :pending_msgs_limit
72
+ # @option options [String] :pending_bytes_limit
73
+ def self.subscribe(nats, type, opts = {}, &callback)
74
+ nats.subscribe(type, opts) do |msg|
75
+ event = Polyn::Serializers::Json.deserialize!(nats, msg.data,
76
+ store_name: opts[:store_name])
77
+ msg.data = event
78
+ callback.call(msg)
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Subscribe to a pull consumer that already exists in the NATS server
84
+ #
85
+ # @param nats [Object] Connected NATS instance from `NATS.connect`
86
+ # @param type [String] The type of event
87
+ # @option options [String] :source - If the `source` portion of the consumer name
88
+ # is more than the `source_root`
89
+ def self.pull_subscribe(nats, type, **opts)
90
+ Polyn::PullSubscriber.new({ nats: nats, type: type, source: opts[:source] })
91
+ end
92
+
93
+ # nats-pure will create a consumer if the one you passed does not exist.
94
+ # Polyn wants to avoid this functionality and instead encourage
95
+ # consumer creation in the centralized `events` codebase so that
96
+ # it's documented, discoverable, and polyn-cli can manage it
97
+ def self.validate_consumer_exists!(nats, stream, consumer_name)
98
+ nats.jetstream.consumer_info(stream, consumer_name)
99
+ rescue NATS::JetStream::Error::NotFound
100
+ raise Polyn::Errors::ValidationError,
101
+ "Consumer #{consumer_name} does not exist. Use polyn-cli to create "\
102
+ "it before attempting to subscribe"
103
+ end
104
+
105
+ ##
106
+ # Configuration information for Polyn
107
+ def self.configuration
108
+ @configuration ||= Configuration.new
109
+ end
110
+
111
+ ##
112
+ # Configuration block to configure Polyn
113
+ def self.configure
114
+ yield(configuration)
115
+ end
116
+ end
data/polyn.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021-2022 Spiff, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ # software and associated documentation files (the "Software"), to deal in the Software
7
+ # without restriction, including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or
12
+ # substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ require_relative "lib/polyn/version"
21
+
22
+ Gem::Specification.new do |spec|
23
+ spec.name = "polyn"
24
+ spec.version = Polyn::VERSION
25
+ spec.authors = ["Jarod", "Brandyn Bennett"]
26
+ spec.email = ["jarod.reid@spiff.com", "brandyn.bennett@spiff.com"]
27
+
28
+ spec.summary = "Polyn Service Framework"
29
+ spec.description = "A lightweight reactive microservice framework."
30
+ spec.homepage = "https://github.com/spiffinc/polyn-ruby"
31
+ spec.license = "MIT"
32
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
33
+
34
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
35
+
36
+ spec.metadata["homepage_uri"] = spec.homepage
37
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
38
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
39
+
40
+ # Specify which files should be added to the gem when it is released.
41
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
42
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
43
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
44
+ end
45
+ spec.bindir = "exe"
46
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
47
+ spec.require_paths = ["lib"]
48
+
49
+ spec.add_dependency "json_schemer", "~> 0.2"
50
+ spec.add_dependency "nats-pure", "~> 2.0"
51
+ spec.add_dependency "yard", "~> 0.9"
52
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: polyn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jarod
8
+ - Brandyn Bennett
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2022-08-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json_schemer
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: nats-pure
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '2.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: yard
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.9'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.9'
56
+ description: A lightweight reactive microservice framework.
57
+ email:
58
+ - jarod.reid@spiff.com
59
+ - brandyn.bennett@spiff.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".github/workflows/ruby.yml"
65
+ - ".gitignore"
66
+ - ".rspec"
67
+ - ".rubocop.yml"
68
+ - ".tool-versions"
69
+ - ".travis.yml"
70
+ - CHANGELOG.md
71
+ - CODE_OF_CONDUCT.md
72
+ - Gemfile
73
+ - Gemfile.lock
74
+ - LICENSE.txt
75
+ - README.md
76
+ - Rakefile
77
+ - bin/console
78
+ - bin/setup
79
+ - docker-compose.yml
80
+ - lib/cloud-event-schema.json
81
+ - lib/polyn.rb
82
+ - lib/polyn/cloud_event.rb
83
+ - lib/polyn/configuration.rb
84
+ - lib/polyn/errors/configuration_error.rb
85
+ - lib/polyn/errors/error.rb
86
+ - lib/polyn/errors/errors.rb
87
+ - lib/polyn/errors/schema_error.rb
88
+ - lib/polyn/errors/validation_error.rb
89
+ - lib/polyn/event.rb
90
+ - lib/polyn/naming.rb
91
+ - lib/polyn/pull_subscriber.rb
92
+ - lib/polyn/schema_store.rb
93
+ - lib/polyn/serializers/json.rb
94
+ - lib/polyn/utils/hash.rb
95
+ - lib/polyn/utils/string.rb
96
+ - lib/polyn/utils/utils.rb
97
+ - lib/polyn/version.rb
98
+ - polyn.gemspec
99
+ homepage: https://github.com/spiffinc/polyn-ruby
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ homepage_uri: https://github.com/spiffinc/polyn-ruby
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 2.4.0
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.2.33
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Polyn Service Framework
123
+ test_files: []