service_mesh_nats 0.5.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20a1213855e080bc361b292f2708b4f023ba38a5051c3ebddffc39cf55b91f7c
4
+ data.tar.gz: fd6939fad816e2beb146fbd8c3697b1221dabb3938a08bdbd73c23664488d71f
5
+ SHA512:
6
+ metadata.gz: 411c35df69a5c8bf43b9ce66e088d666e129c34e64e93a6be3408a471775813f427cb32813a1e47a49bdbaa0e69f56e449ee8e73ee720c397646a70b187c6c0e
7
+ data.tar.gz: 9fbde8fda5e37e58e4d08b0ffe9fe341098bdf5f84d241ddba5bace38003fcf974517bd2cd009deeb5f28cf45f828e3f1f79aa48f2593e1eec4c8f2355dd5f55
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paymentbox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # service-mesh-nats-ruby
2
+
3
+ `service_mesh_nats` is the NATS transport implementation in Ruby for the
4
+ [Service Mesh API Specification](https://github.com/Paymentbox-com/service-mesh-api).
5
+
6
+ This gem depends on the contract types and errors in [service-mesh-ruby](https://github.com/Paymentbox-com/service-mesh-ruby).
7
+
8
+ ## Install
9
+
10
+ ```ruby
11
+ # Gemfile
12
+ gem "service_mesh_nats"
13
+ ```
14
+
15
+ Requires Ruby 3.3 or newer and a reachable NATS server. Depends on
16
+ `service_mesh` and `nats-pure`.
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ require "service_mesh_nats"
22
+
23
+ echo = ServiceMesh::Target.new(segments: %w[demo echo], kind: :route)
24
+ created = ServiceMesh::Target.new(segments: %w[demo created], kind: :topic)
25
+ map = ServiceMesh::ServiceMap.new(targets: [echo, created])
26
+
27
+ config = {
28
+ "url" => "nats://127.0.0.1:4222",
29
+ "deployment_group" => "demo"
30
+ }
31
+
32
+ client = ServiceMeshNats::Client.new(config, map)
33
+ # Connects. Raises BadConfig or the connection failure.
34
+
35
+ runtime = ServiceMeshNats::Runtime.new(client, config,
36
+ endpoints: [
37
+ ServiceMesh::Endpoint.new(target: echo, handler: ->(m) { ServiceMesh::Message.new(target: echo, payload: m.payload) })
38
+ ],
39
+ subscribers: [
40
+ ServiceMesh::Subscriber.new(target: created, handler: ->(m) { puts "created: #{m.payload}" })
41
+ ])
42
+ # Raises NoDeploymentGroup, BadConfig, KindMismatch, or InvalidTarget, and
43
+ # ArgumentError for a handler that does not respond to call.
44
+
45
+ runtime.start
46
+ at_exit { runtime.stop(10) }
47
+
48
+ reply = client.request(ServiceMesh::Message.new(target: echo, payload: "hi"))
49
+ client.publish(ServiceMesh::Message.new(target: created, payload: "order 42"))
50
+ ```
51
+
52
+ The client is the runtime's connection. `runtime.client` returns it, and
53
+ `runtime.stop` closes it. A process that only requests and
54
+ publishes builds a client itself and calls `close` when done.
55
+ `runtime.service_map` and `client.service_map` return the map the client was
56
+ built with.
57
+
58
+ ## Documentation
59
+
60
+ - [Examples](docs/examples.md): a server process, a call-only client process, and consumer groups
61
+ - [Public API](docs/public-api.md): the constants this gem exposes and how `Client` and `Runtime` behave
62
+ - [What the NATS Runtime Decides](docs/runtime-behavior.md): targets, configuration, metadata, delivery, handler failure, timeouts, concurrency, lifecycle, and transport errors
63
+ - [Development](docs/development.md): the recipes and the tests
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nats/io/client"
4
+
5
+ module ServiceMeshNats
6
+ # Owns one NATS connection and sends messages over it. A Runtime is built
7
+ # from a client and subscribes through its connection.
8
+ class Client
9
+ # The map this client was built with. The client does not otherwise use
10
+ # it.
11
+ attr_reader :service_map
12
+
13
+ # Parses the connection keys of +config+ (every other key is ignored) and
14
+ # opens the connection. A connection failure passes through unchanged.
15
+ # Connection errors reported by nats-pure after that are logged to
16
+ # +logger+ when one is given.
17
+ def initialize(config, service_map, logger: nil)
18
+ @settings = ClientSettings.parse(config)
19
+ @service_map = service_map
20
+ @lock = Mutex.new
21
+ @closed = false
22
+ @nc = NATS::IO::Client.new
23
+ @nc.on_error { |e| logger.warn("service_mesh_nats: #{e.class}: #{e.message}") } if logger
24
+ @nc.connect(@settings.connect_options)
25
+ end
26
+
27
+ # Closes the connection and marks the client closed. Returns nil.
28
+ # Idempotent.
29
+ def close
30
+ @lock.synchronize do
31
+ return nil if @closed
32
+
33
+ @nc.close
34
+ @closed = true
35
+ end
36
+ nil
37
+ end
38
+
39
+ # The NATS::IO::Client this client owns. Used by Runtime. Raises Closed
40
+ # after close.
41
+ def connection
42
+ raise Closed if @closed
43
+
44
+ @nc
45
+ end
46
+
47
+ # Sends +message+ to a route target and returns the reply.
48
+ #
49
+ # +opts+ may carry REQUEST_TIMEOUT_KEY to override the configured request
50
+ # timeout for this call. Expiry raises NATS::Timeout; no receiver raises
51
+ # NATS::IO::NoRespondersError.
52
+ def request(message, opts = {})
53
+ Subject.check_kind!(message.target, :route, "request")
54
+ subject = Subject.format(message.target)
55
+ timeout = Settings.duration(opts.to_h, REQUEST_TIMEOUT_KEY, @settings.request_timeout)
56
+ nc = connection
57
+
58
+ reply = nc.request_msg(nats_msg(subject, message), timeout: timeout)
59
+ header = reply.header || {}
60
+ raise HandlerError.new(header[HANDLER_ERROR_HEADER].to_s) if header.key?(HANDLER_ERROR_HEADER)
61
+
62
+ ServiceMesh::Message.new(target: message.target, metadata: header, payload: reply.data.to_s)
63
+ end
64
+
65
+ # Sends +message+ to a topic target. +opts+ is accepted for the interface
66
+ # and not read.
67
+ def publish(message, opts = {})
68
+ Subject.check_kind!(message.target, :topic, "publish")
69
+ subject = Subject.format(message.target)
70
+ connection.publish_msg(nats_msg(subject, message))
71
+ nil
72
+ end
73
+
74
+ private
75
+
76
+ def nats_msg(subject, message)
77
+ header = message.metadata.empty? ? nil : message.metadata.transform_keys(&:to_s).transform_values(&:to_s)
78
+ NATS::Msg.new(subject: subject, data: message.payload, header: header)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceMeshNats
4
+ # This transport's errors. The contract errors are ServiceMesh::KindMismatch,
5
+ # ServiceMesh::InvalidTarget, and ServiceMesh::NoDeploymentGroup.
6
+ class Error < StandardError; end
7
+
8
+ class BadConfig < Error; end
9
+
10
+ class Closed < Error
11
+ def initialize(msg = "client has been closed") = super
12
+ end
13
+
14
+ class AlreadyStarted < Error
15
+ def initialize(msg = "runtime already started") = super
16
+ end
17
+
18
+ class Stopped < Error
19
+ def initialize(msg = "runtime has been stopped; it is not restartable") = super
20
+ end
21
+
22
+ # Raised by Client#request when the serving handler raised. +text+ is the
23
+ # handler's error message as sent by the serving runtime.
24
+ class HandlerError < Error
25
+ attr_reader :text
26
+
27
+ def initialize(text)
28
+ @text = text
29
+ super("handler failed: #{text}")
30
+ end
31
+ end
32
+
33
+ # Reply header set when an endpoint handler fails. Application metadata
34
+ # should not use this key.
35
+ HANDLER_ERROR_HEADER = "Mesh-Handler-Error"
36
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+ require "logger"
5
+ require "nats/io/client"
6
+
7
+ module ServiceMeshNats
8
+ # The service process: subscribes endpoints and subscribers on one NATS
9
+ # connection and runs their handlers on a bounded thread pool.
10
+ class Runtime
11
+ # A validated Endpoint or Subscriber. queue nil means a plain subscription.
12
+ Binding = Data.define(:subject, :queue, :target, :handler, :replies)
13
+
14
+ FLUSH_BUDGET = 5.0
15
+
16
+ # The client this runtime was built from, in every state.
17
+ attr_reader :client
18
+
19
+ # Reads ServiceMesh::DEPLOYMENT_GROUP_KEY and CONCURRENCY_KEY from +config+;
20
+ # connection keys are ignored, the client carries them. Raises
21
+ # ServiceMesh::NoDeploymentGroup, BadConfig, ServiceMesh::KindMismatch, or
22
+ # ServiceMesh::InvalidTarget. Two bindings on one subject become two
23
+ # subscriptions. The connection is not touched until start.
24
+ def initialize(client, config, endpoints: [], subscribers: [], logger: Logger.new($stderr))
25
+ @settings = RuntimeSettings.parse(config)
26
+ @client = client
27
+ @logger = logger
28
+ @bindings = bind_all(endpoints, subscribers)
29
+
30
+ @lock = Mutex.new
31
+ @state = :created
32
+ @subs = []
33
+ @pool = nil
34
+ end
35
+
36
+ def running?
37
+ @state == :running
38
+ end
39
+
40
+ # The client's service map.
41
+ def service_map
42
+ @client.service_map
43
+ end
44
+
45
+ # Subscribes every binding on the client's connection and begins
46
+ # receiving. Raises AlreadyStarted on a running runtime, Stopped after
47
+ # stop, and Closed when the client has been closed. A failure while
48
+ # subscribing closes the client, marks the runtime stopped, and passes
49
+ # through.
50
+ def start
51
+ @lock.synchronize do
52
+ raise AlreadyStarted if @state == :running
53
+ raise Stopped if @state == :stopped
54
+
55
+ nc = @client.connection
56
+
57
+ pool = Concurrent::FixedThreadPool.new(@settings.concurrency, name: "service_mesh_nats")
58
+ subs = []
59
+ begin
60
+ @bindings.each do |b|
61
+ opts = b.queue ? {queue: b.queue} : {}
62
+ subs << nc.subscribe(b.subject, opts) { |msg| dispatch(pool, nc, b, msg) }
63
+ end
64
+ # Ensure the server has every subscription before start returns.
65
+ nc.flush(FLUSH_BUDGET)
66
+ rescue => e
67
+ subs.each { |s| s.unsubscribe rescue nil } # rubocop:disable Style/RescueModifier
68
+ pool.kill
69
+ @client.close
70
+ @state = :stopped
71
+ raise e
72
+ end
73
+
74
+ @subs = subs
75
+ @pool = pool
76
+ @state = :running
77
+ end
78
+ nil
79
+ end
80
+
81
+ # Stops receiving, waits up to +drain+ seconds for in-flight handlers,
82
+ # flushes, then closes the client. Returns true when every handler
83
+ # finished, false when some were abandoned. A runtime that is not running
84
+ # returns true and leaves the client as it is. A client the caller closed
85
+ # directly is treated as gone: nothing is flushed and the drain result is
86
+ # still returned.
87
+ def stop(drain)
88
+ drain = Float(drain)
89
+ raise ArgumentError, "drain must be non-negative" if drain.negative?
90
+
91
+ @lock.synchronize do
92
+ return true unless @state == :running
93
+
94
+ @state = :stopped
95
+ @subs.each { |s| s.unsubscribe rescue nil } # rubocop:disable Style/RescueModifier
96
+
97
+ @pool.shutdown
98
+ finished = @pool.wait_for_termination(drain)
99
+ flush if finished
100
+ @client.close
101
+ @subs = []
102
+ finished
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def flush
109
+ @client.connection.flush(FLUSH_BUDGET)
110
+ rescue Closed
111
+ # The caller closed the client directly; there is nothing to flush.
112
+ rescue => e
113
+ @logger.warn("service_mesh_nats: flush during stop failed: #{e.message}")
114
+ end
115
+
116
+ def bind_all(endpoints, subscribers)
117
+ bind = lambda do |target, want, use, metadata, handler, replies|
118
+ Subject.check_kind!(target, want, use)
119
+ subject = Subject.format(target)
120
+ raise ArgumentError, "#{use} #{subject} handler must respond to call" unless handler.respond_to?(:call)
121
+
122
+ queue = Subject.consumer_group(metadata, target.metadata, @settings.deployment_group)
123
+ Binding.new(subject: subject, queue: queue, target: target, handler: handler, replies: replies)
124
+ end
125
+
126
+ endpoints.map { |e| bind.call(e.target, :route, "Endpoint", e.metadata, e.handler, true) } +
127
+ subscribers.map { |s| bind.call(s.target, :topic, "Subscriber", s.metadata, s.handler, false) }
128
+ end
129
+
130
+ # Runs on nats-pure's subscription thread; hands the message to the pool.
131
+ def dispatch(pool, nc, binding, msg)
132
+ pool.post { serve(nc, binding, msg) }
133
+ rescue Concurrent::RejectedExecutionError
134
+ # The pool is shutting down; the subscription is already gone.
135
+ end
136
+
137
+ def serve(nc, binding, msg)
138
+ inbound = ServiceMesh::Message.new(target: binding.target, metadata: msg.header || {}, payload: msg.data.to_s)
139
+
140
+ out = binding.handler.call(inbound)
141
+ return unless binding.replies && msg.reply
142
+
143
+ unless out.is_a?(ServiceMesh::Message)
144
+ raise TypeError, "endpoint #{binding.subject} handler returned #{out.class}, expected ServiceMesh::Message"
145
+ end
146
+
147
+ reply(nc, msg.reply, out.metadata, out.payload)
148
+ rescue => e
149
+ @logger.error("service_mesh_nats: handler failed on #{binding.subject}: #{e.class}: #{e.message}")
150
+ reply(nc, msg.reply, {HANDLER_ERROR_HEADER => e.message}, "") if binding.replies && msg.reply
151
+ end
152
+
153
+ def reply(nc, subject, metadata, payload)
154
+ header = metadata.empty? ? nil : metadata.transform_keys(&:to_s).transform_values(&:to_s)
155
+ nc.publish_msg(NATS::Msg.new(subject: subject, data: payload.to_s.b, header: header))
156
+ rescue => e
157
+ @logger.error("service_mesh_nats: reply failed: #{e.class}: #{e.message}")
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+
5
+ module ServiceMeshNats
6
+ # Configuration keys this transport reads beyond ServiceMesh::DEPLOYMENT_GROUP_KEY.
7
+ # Every value is a string. Durations are seconds, such as "5" or "0.25".
8
+ URL_KEY = "url"
9
+ NAME_KEY = "name"
10
+ CONNECT_TIMEOUT_KEY = "connect_timeout"
11
+ REQUEST_TIMEOUT_KEY = "request_timeout"
12
+ CONCURRENCY_KEY = "concurrency"
13
+
14
+ DEFAULT_URL = "nats://127.0.0.1:4222"
15
+ DEFAULT_CONNECT_TIMEOUT = 5.0
16
+ DEFAULT_REQUEST_TIMEOUT = 30.0
17
+
18
+ # Value parsers shared by the two settings types.
19
+ module Settings
20
+ module_function
21
+
22
+ # Reads +key+ as a positive number of seconds, or returns +fallback+ when
23
+ # absent.
24
+ def duration(hash, key, fallback)
25
+ return fallback unless hash.key?(key)
26
+
27
+ value = Float(hash[key], exception: false)
28
+ raise BadConfig, "#{key}=#{hash[key].inspect} must be a positive number of seconds" if value.nil? || value <= 0
29
+
30
+ value
31
+ end
32
+
33
+ def positive_integer(hash, key, fallback)
34
+ return fallback unless hash.key?(key)
35
+
36
+ value = Integer(hash[key], 10, exception: false)
37
+ raise BadConfig, "#{key}=#{hash[key].inspect} must be a positive integer" if value.nil? || value <= 0
38
+
39
+ value
40
+ end
41
+ end
42
+
43
+ # The keys a Client reads. Any other key is ignored.
44
+ ClientSettings = Data.define(:url, :name, :connect_timeout, :request_timeout) do
45
+ def self.parse(config)
46
+ config = config.to_h
47
+ new(
48
+ url: config.fetch(URL_KEY, DEFAULT_URL).then { |v| v.to_s.empty? ? DEFAULT_URL : v },
49
+ name: config[NAME_KEY],
50
+ connect_timeout: Settings.duration(config, CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT),
51
+ request_timeout: Settings.duration(config, REQUEST_TIMEOUT_KEY, DEFAULT_REQUEST_TIMEOUT)
52
+ )
53
+ end
54
+
55
+ def connect_options
56
+ opts = {servers: [url], connect_timeout: connect_timeout}
57
+ opts[:name] = name if name && !name.empty?
58
+ opts
59
+ end
60
+ end
61
+
62
+ # The keys a Runtime reads. Connection keys in a runtime's config are
63
+ # ignored; the runtime's client carries them.
64
+ RuntimeSettings = Data.define(:deployment_group, :concurrency) do
65
+ def self.parse(config)
66
+ config = config.to_h
67
+ deployment_group = config[ServiceMesh::DEPLOYMENT_GROUP_KEY].to_s
68
+ raise ServiceMesh::NoDeploymentGroup if deployment_group.empty?
69
+
70
+ new(
71
+ deployment_group: deployment_group,
72
+ concurrency: Settings.positive_integer(config, CONCURRENCY_KEY, Etc.nprocessors)
73
+ )
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceMeshNats
4
+ # Assembles targets into NATS subjects and resolves queue groups.
5
+ module Subject
6
+ module_function
7
+
8
+ # Joins segments with "." or raises InvalidTarget with the reason.
9
+ def format(target)
10
+ raise ServiceMesh::InvalidTarget, "no segments" if target.segments.empty?
11
+
12
+ target.segments.each_with_index do |seg, i|
13
+ reason = segment_problem(seg)
14
+ raise ServiceMesh::InvalidTarget, "segment #{i} #{seg.inspect}: #{reason}" if reason
15
+ end
16
+ target.segments.join(".")
17
+ end
18
+
19
+ def segment_problem(seg)
20
+ return "empty" if seg.empty?
21
+ return "contains '.'" if seg.include?(".")
22
+ return "contains wildcard" if seg.include?("*") || seg.include?(">")
23
+ return "contains whitespace" if seg.match?(/\s/)
24
+ return "contains non-printable character" if seg.match?(/[^[:print:]]/)
25
+
26
+ nil
27
+ end
28
+
29
+ # Raises KindMismatch unless the target's kind is +want+.
30
+ def check_kind!(target, want, use)
31
+ return if target.kind == want
32
+
33
+ raise ServiceMesh::KindMismatch, "#{use} requires a #{want} target, got #{target.kind}"
34
+ end
35
+
36
+ # The queue group for a binding. Binding metadata wins, then target
37
+ # metadata, then the deployment group. nil means a plain subscription.
38
+ def consumer_group(binding_metadata, target_metadata, deployment_group)
39
+ [binding_metadata, target_metadata].each do |md|
40
+ next unless md.key?(ServiceMesh::CONSUMER_GROUP_KEY)
41
+
42
+ value = md[ServiceMesh::CONSUMER_GROUP_KEY].to_s
43
+ next if value.empty?
44
+ return nil if value == ServiceMesh::CONSUMER_GROUP_NONE
45
+
46
+ return value
47
+ end
48
+ deployment_group
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceMeshNats
4
+ VERSION = "0.5.1"
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "service_mesh"
4
+
5
+ require_relative "service_mesh_nats/version"
6
+ require_relative "service_mesh_nats/errors"
7
+ require_relative "service_mesh_nats/settings"
8
+ require_relative "service_mesh_nats/subject"
9
+ require_relative "service_mesh_nats/client"
10
+ require_relative "service_mesh_nats/runtime"
11
+
12
+ # NATS transport for the Service Mesh API Specification. The contract types
13
+ # and errors come from the service_mesh gem.
14
+ module ServiceMeshNats
15
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_mesh_nats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Bryant Morrill
8
+ - Paymentbox
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2026-09-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nats-pure
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.5'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.5'
28
+ - !ruby/object:Gem::Dependency
29
+ name: service_mesh
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.4'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.4'
42
+ description:
43
+ email:
44
+ - bryantreadmorrill@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - LICENSE
50
+ - README.md
51
+ - lib/service_mesh_nats.rb
52
+ - lib/service_mesh_nats/client.rb
53
+ - lib/service_mesh_nats/errors.rb
54
+ - lib/service_mesh_nats/runtime.rb
55
+ - lib/service_mesh_nats/settings.rb
56
+ - lib/service_mesh_nats/subject.rb
57
+ - lib/service_mesh_nats/version.rb
58
+ homepage: https://github.com/Paymentbox-com/service-mesh-nats-ruby
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/Paymentbox-com/service-mesh-nats-ruby
63
+ source_code_uri: https://github.com/Paymentbox-com/service-mesh-nats-ruby
64
+ rubygems_mfa_required: 'true'
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '3.3'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.5.22
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Ruby implementation of the Service Mesh API Specification over NATS
84
+ test_files: []