async-grpc 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b2db42e03f75834e2259977d953d8c1688949e3c45c3ca972f9a9a0c6ca23b9
4
- data.tar.gz: 1f7ce2103876bcb8fe0d716595668bee4904e1d2f35d0cb5ac86b246f84fd2e0
3
+ metadata.gz: 031cde64ff322b5e4d247cec205cdbe17219d455240866df154fca3f370e675c
4
+ data.tar.gz: d926dbe26788e37bbcb219cce9224c40f5c282bb0ed84c42fcbb6118472da69f
5
5
  SHA512:
6
- metadata.gz: 61cfba13b45b83a390b41e2ede94ad6bf81286f3273630d8bc8336103e40d888fbb0d0c74f1f54c04c44d37a19fa493fcb0cf29e096388d855ff3b7fb626d187
7
- data.tar.gz: 46dc51138f257f1768ddef3d81b42d1c040a53812a9970bd8bb7fee60a94a6e0d16f210069faa4c6b484f58994070f1e9ad04459685f57932fa89db40eb5bf8c
6
+ metadata.gz: 5a5cb6a72c70046df6b85f287d96f04b2ab12a807945025b5c38b924f6dcb5480b012444b42b4e857e63f1b535c5c6db9cd9c6fe6ad9f04c6f7976065cdcfbf5
7
+ data.tar.gz: dc65cc27dfcb838ce8b77f805012333edeb0813707e9d646a805c9776f9e7fae9d2dca78cd3a5dc29ceb27b99ff435cb9f84adcbeba131362aeb40ef7dc37020
checksums.yaml.gz.sig CHANGED
Binary file
@@ -126,6 +126,49 @@ Async do
126
126
  end
127
127
  ```
128
128
 
129
+ ### Instrumentation
130
+
131
+ Subclass the dispatcher and override {ruby Async::GRPC::Dispatcher#emit_completion} to record completion metrics or logs.
132
+ The hook runs once per request after the final gRPC status is assigned, including routing failures and completed streaming calls:
133
+
134
+ ``` ruby
135
+ class InstrumentedDispatcher < Async::GRPC::Dispatcher
136
+ protected
137
+
138
+ def emit_completion(call, error = nil)
139
+ Console.info(self, "Request completed!", path: call.request.path, status: call.status)
140
+ end
141
+ end
142
+ ```
143
+
144
+ A cancelled request has a status of `Protocol::GRPC::Status::CANCELLED` and no `error`. Exceptions raised by the hook are logged and ignored.
145
+
146
+ Translate expected application errors at the service boundary by raising {ruby Protocol::GRPC::Error}:
147
+
148
+ ``` ruby
149
+ class ApplicationService < Async::GRPC::Service
150
+ def find_record(input, output, call)
151
+ output.write(Record.find(input.read.id))
152
+ rescue ActiveRecord::RecordNotFound => error
153
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::NOT_FOUND, error.message)
154
+ end
155
+ end
156
+ ```
157
+
158
+ Override {ruby Async::GRPC::Dispatcher#invoke_service} when you need middleware around service execution:
159
+
160
+ ``` ruby
161
+ class ApplicationDispatcher < Async::GRPC::Dispatcher
162
+ protected
163
+
164
+ def invoke_service(service, handler_method, input, output, call)
165
+ RequestContext.for(call.request) do
166
+ super
167
+ end
168
+ end
169
+ end
170
+ ```
171
+
129
172
  ## Complete Example
130
173
 
131
174
  ``` ruby
data/context/index.yaml CHANGED
@@ -3,6 +3,8 @@
3
3
  ---
4
4
  description: Client and server implementation for gRPC using Async.
5
5
  metadata:
6
+ bug_tracker_uri: https://github.com/socketry/async-grpc/issues
7
+ changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
6
8
  documentation_uri: https://socketry.github.io/async-grpc/
7
9
  source_code_uri: https://github.com/socketry/async-grpc.git
8
10
  files:
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require "async"
7
8
  require "async/http/client"
@@ -10,7 +11,6 @@ require "async/http/endpoint"
10
11
  require "protocol/http"
11
12
  require "protocol/grpc"
12
13
  require "protocol/grpc/interface"
13
- require "protocol/grpc/methods"
14
14
  require "protocol/grpc/body/readable_body"
15
15
  require "protocol/grpc/body/writable_body"
16
16
  require "protocol/grpc/metadata"
@@ -123,7 +123,7 @@ module Async
123
123
  raise ArgumentError, "Unknown method: #{method}" unless rpc
124
124
 
125
125
  path = service.path(method)
126
- headers = Protocol::GRPC::Methods.build_headers(
126
+ headers = Protocol::GRPC::Metadata.build(
127
127
  metadata: metadata,
128
128
  timeout: timeout,
129
129
  content_type: "application/grpc+proto"
@@ -277,6 +277,7 @@ module Async
277
277
  # @parameter initial [Object | Array | Nil] Optional initial message(s) to write before waiting for the response
278
278
  # @yields {|input, output| ...} Block to handle bidirectional streaming
279
279
  # @returns [Protocol::GRPC::Body::ReadableBody] Readable body for streaming messages
280
+ # Without a block, `initial` is sent as the complete request stream and the caller owns the returned readable body.
280
281
  # @raises [Protocol::GRPC::Error] If the gRPC call fails
281
282
  def bidirectional_call(path, headers, request_class, response_class, encoding, initial: nil, &block)
282
283
  body = Protocol::GRPC::Body::WritableBody.new(
@@ -284,6 +285,7 @@ module Async
284
285
  message_class: request_class
285
286
  )
286
287
  Array(initial).each{|message| body.write(message)}
288
+ body.close_write unless block_given?
287
289
 
288
290
  http_request = Protocol::HTTP::Request["POST", path, headers, body]
289
291
  response = call(http_request)
@@ -298,6 +300,8 @@ module Async
298
300
  )
299
301
 
300
302
  unless block_given?
303
+ # The caller owns the readable body and its underlying response:
304
+ response = nil
301
305
  return readable_body
302
306
  end
303
307
 
@@ -328,7 +332,7 @@ module Async
328
332
  return if status == Protocol::GRPC::Status::OK
329
333
 
330
334
  message = Protocol::GRPC::Metadata.extract_message(response.headers)
331
- metadata = Protocol::GRPC::Methods.extract_metadata(response.headers)
335
+ metadata = Protocol::GRPC::Metadata.extract(response.headers)
332
336
 
333
337
  remote_error = RemoteError.for(message, metadata)
334
338
 
@@ -2,14 +2,15 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require "async"
7
8
 
8
9
  require_relative "error"
9
10
 
10
11
  require "protocol/grpc/middleware"
11
- require "protocol/grpc/methods"
12
12
  require "protocol/grpc/call"
13
+ require "protocol/grpc/route"
13
14
  require "protocol/grpc/body/readable_body"
14
15
  require "protocol/grpc/body/writable_body"
15
16
  require "protocol/grpc/metadata"
@@ -45,11 +46,37 @@ module Async
45
46
 
46
47
  protected
47
48
 
48
- # Invoke a service handler, providing a protected extension point for middleware and instrumentation around service execution.
49
+ # Called once after the final gRPC status has been assigned.
49
50
  #
50
- # Override this method to observe service outcomes, establish request context, or wrap handlers with application middleware. Overrides should call `super` to invoke the handler and preserve the dispatcher's stream cleanup, trailer handling, and successful status assignment.
51
+ # Override this method to record request metrics or logs. The hook also runs for
52
+ # routing/setup failures and after streaming calls finish. A cancelled call has
53
+ # a status of `Protocol::GRPC::Status::CANCELLED` and no error.
51
54
  #
52
- # This hook only surrounds service handler execution. Errors raised earlier while routing or preparing the request do not pass through it. A call deadline that expires during service execution raises {DeadlineExceededError} through this hook before the dispatcher converts it to a `DEADLINE_EXCEEDED` response.
55
+ # Exceptions raised by this hook are ignored.
56
+ #
57
+ # @parameter call [Protocol::GRPC::Call] The call context, including the request and the response.
58
+ # @parameter error [Exception | Nil] The error which caused the call to fail, if any.
59
+ # @returns [void]
60
+ # @example Record the final gRPC status code
61
+ # def emit_completion(call, error = nil)
62
+ # service_name, method_name = Protocol::GRPC::Route.parse(call.request.path)
63
+ # metrics.increment("grpc.request", tags: {service: service_name, method: method_name, status: call.status})
64
+ # end
65
+ def emit_completion(call, error = nil)
66
+ # Implementation-defined.
67
+ end
68
+
69
+ # Invoke a service handler.
70
+ #
71
+ # Override this method to establish request context or wrap handlers with
72
+ # application middleware. Overrides should call `super` to invoke the handler.
73
+ # Stream cleanup, trailer handling, and status assignment are performed by the
74
+ # dispatcher after this method returns or raises.
75
+ #
76
+ # This hook only surrounds service execution. Use {emit_completion} to observe
77
+ # every request, including routing/setup failures. A deadline expiring during
78
+ # service execution raises {DeadlineExceededError} through this hook before it is
79
+ # converted to `DEADLINE_EXCEEDED`.
53
80
  #
54
81
  # @parameter service [Async::GRPC::Service] The service containing the handler.
55
82
  # @parameter handler_method [Symbol] The service method to invoke.
@@ -65,98 +92,183 @@ module Async
65
92
  # end
66
93
  # end
67
94
  def invoke_service(service, handler_method, input, output, call)
95
+ service.send(handler_method, input, output, call)
96
+ end
97
+
98
+ # Map an internal dispatcher error to the gRPC status and message reported to the client.
99
+ #
100
+ # @parameter error [Exception] The error which caused the call to fail.
101
+ # @returns [Tuple(Integer, String | Nil)] The gRPC status code and message.
102
+ private def status_for(error)
103
+ case error
104
+ when DeadlineExceededError
105
+ return Protocol::GRPC::Status::DEADLINE_EXCEEDED, "Deadline exceeded!"
106
+ when Protocol::GRPC::Error
107
+ return error.status_code, error.message
108
+ else
109
+ # A `nil` message defers to the message of the error itself:
110
+ return Protocol::GRPC::Status::INTERNAL, nil
111
+ end
112
+ end
113
+
114
+ # Invoke the service with deadline enforcement and completion reporting.
115
+ #
116
+ # @parameter parent [Async::Task] The task used to enforce the call deadline.
117
+ def dispatch_to_service(service, handler_method, input, output, call, parent: Async::Task.current)
118
+ error = nil
119
+ cancellation = nil
120
+
68
121
  begin
69
- service.send(handler_method, input, output, call)
70
- ensure
71
- # Close input stream:
72
- input.close
122
+ if deadline = call.deadline
123
+ parent.with_timeout(deadline.remaining, DeadlineExceededError) do
124
+ invoke_service(service, handler_method, input, output, call)
125
+ end
126
+ else
127
+ invoke_service(service, handler_method, input, output, call)
128
+ end
129
+ rescue => error
130
+ prepare_trailers(output, call)
131
+ assign_status(call, error)
132
+ rescue Async::Stop => cancellation
133
+ prepare_trailers(output, call)
73
134
 
74
- # Close output stream:
75
- output.close_write unless output.closed?
135
+ # Cancellation has a fixed status and does not require error mapping:
136
+ if headers = call.response&.headers
137
+ Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::CANCELLED)
138
+ end
76
139
 
77
- # gRPC supports trailers-only responses, but only if there are no data frames. If at this point, there are data frames (which may or may not have been sent yet), we need to mark trailers:
78
- if output.count > 0
79
- call.response.headers.trailer!
140
+ raise
141
+ else
142
+ prepare_trailers(output, call)
143
+
144
+ # Add status (if not already set by handler):
145
+ if headers = call.response&.headers
146
+ unless headers.key?("grpc-status")
147
+ Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
148
+ end
80
149
  end
150
+ ensure
151
+ finalize_streams(input, output, call, error, cancellation)
81
152
  end
82
-
83
- # Add status (if not already set by handler):
84
- if headers = call.response&.headers
85
- # Only add OK status if grpc-status hasn't been set by the handler:
86
- unless headers.key?("grpc-status")
87
- Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
88
- end
153
+ end
154
+
155
+ # Mark response headers as trailers when data was written.
156
+ def prepare_trailers(output, call)
157
+ if output.count > 0
158
+ call.response.headers.trailer!
89
159
  end
90
160
  end
91
161
 
92
- def dispatch_to_service(service, handler_method, input, output, call, parent: Async::Task.current)
93
- if deadline = call.deadline
94
- parent.with_timeout(deadline.remaining, DeadlineExceededError) do
95
- invoke_service(service, handler_method, input, output, call)
96
- end
162
+ # Close the input and output streams.
163
+ def close_streams(input, output, call)
164
+ # Close input stream:
165
+ input.close
166
+ ensure
167
+ # Close output stream:
168
+ unless output.closed?
169
+ output.close_write
170
+ end
171
+ end
172
+
173
+ # Close streams and report completion while preserving an active service error or cancellation.
174
+ private def finalize_streams(input, output, call, error, cancellation)
175
+ close_streams(input, output, call)
176
+ rescue => close_error
177
+ if error || cancellation
178
+ Console.warn(self, "Error during stream cleanup!", exception: close_error)
97
179
  else
98
- invoke_service(service, handler_method, input, output, call)
180
+ error = close_error
181
+ assign_status(call, error)
182
+ raise
99
183
  end
100
- rescue DeadlineExceededError => error
101
- # Close input and output streams:
102
- input.close
103
- output.close_write unless output.closed?
104
-
105
- # Set DEADLINE_EXCEEDED status in trailers:
184
+ ensure
185
+ # Cancellation raises `Async::Stop`, not `StandardError`:
186
+ report_completion(call, error)
187
+ end
188
+
189
+ # Assign the status for the given error to the response.
190
+ def assign_status(call, error)
106
191
  if headers = call.response&.headers
107
- Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::DEADLINE_EXCEEDED, message: "Deadline exceeded!", error: error)
192
+ begin
193
+ status, message = status_for(error)
194
+ rescue => internal_error
195
+ Console.warn(self, "Error during status mapping!", exception: internal_error)
196
+
197
+ status = Protocol::GRPC::Status::INTERNAL
198
+ message = nil
199
+ end
200
+
201
+ Protocol::GRPC::Metadata.assign_status!(headers, status: status, message: message, error: error)
108
202
  end
109
203
  end
110
204
 
205
+ # Report completion without allowing instrumentation to affect the request.
206
+ private def report_completion(call, error = nil)
207
+ emit_completion(call, error)
208
+ rescue => error
209
+ Console.warn(self, "Error during emit completion!", exception: error)
210
+ end
211
+
111
212
  # Dispatch the request to the appropriate service.
112
213
  # @parameter request [Protocol::HTTP::Request] The HTTP request
113
214
  # @returns [Protocol::HTTP::Response] The HTTP response
114
- # @raises [Protocol::GRPC::Error] If service or method is not found
115
215
  def dispatch(request)
116
- # Parse service and method from path:
117
- service_name, method_name = Protocol::GRPC::Methods.parse_path(request.path)
118
-
119
- # Find service:
120
- service = @services[service_name]
121
- unless service
122
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
123
- end
124
-
125
- # Verify service name matches:
126
- unless service_name == service.service_name
127
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
128
- end
129
-
130
- # Get RPC descriptions from the service:
131
- rpc_descriptor = service.rpc_descriptions[method_name]
132
- unless rpc_descriptor
133
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
134
- end
135
-
136
- handler_method = rpc_descriptor.method
137
- request_class = rpc_descriptor.request_class
138
- response_class = rpc_descriptor.response_class
139
-
140
- # Verify handler method exists:
141
- unless service.respond_to?(handler_method, true)
142
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
143
- end
144
-
145
- # Create protocol-level objects for gRPC handling:
146
- encoding = request.headers["grpc-encoding"]
147
- input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
148
- output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
149
-
150
216
  # Create response headers:
217
+ encoding = request.headers["grpc-encoding"]
151
218
  response_headers = Protocol::HTTP::Headers.new([], nil, policy: Protocol::GRPC::HEADER_POLICY)
152
219
  response_headers["content-type"] = "application/grpc+proto"
153
220
  response_headers["grpc-encoding"] = encoding if encoding
154
221
 
155
- # Create response object:
156
- response = Protocol::HTTP::Response[200, response_headers, output]
222
+ # Assign the body after routing so setup failures are trailers-only:
223
+ response = Protocol::HTTP::Response[200, response_headers, nil]
224
+ call = nil
157
225
 
158
- # Create call context with request, response and deadline:
159
- call = Protocol::GRPC::Call.for(request, response)
226
+ begin
227
+ # Create the call context:
228
+ call = Protocol::GRPC::Call.for(request, response)
229
+
230
+ # Extract the routing information from the request path:
231
+ service_name, method_name = Protocol::GRPC::Route.parse(request.path)
232
+
233
+ # Find service:
234
+ service = @services[service_name]
235
+ unless service
236
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
237
+ end
238
+
239
+ # Verify service name matches:
240
+ unless service_name == service.service_name
241
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
242
+ end
243
+
244
+ # Get RPC descriptions from the service:
245
+ rpc_descriptor = service.rpc_descriptions[method_name]
246
+ unless rpc_descriptor
247
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
248
+ end
249
+
250
+ handler_method = rpc_descriptor.method
251
+ request_class = rpc_descriptor.request_class
252
+ response_class = rpc_descriptor.response_class
253
+
254
+ # Verify handler method exists:
255
+ unless service.respond_to?(handler_method, true)
256
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
257
+ end
258
+
259
+ # Create protocol-level objects for gRPC handling:
260
+ input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
261
+ output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
262
+ response.body = output
263
+ rescue => error
264
+ # Routing/setup failures. {Protocol::GRPC::Call.for} can fail before the call context exists:
265
+ call ||= Protocol::GRPC::Call.new(request, response)
266
+
267
+ assign_status(call, error)
268
+ report_completion(call, error)
269
+
270
+ return response
271
+ end
160
272
 
161
273
  if rpc_descriptor.streaming?
162
274
  Async do |task|
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  module Async
7
8
  module GRPC
@@ -7,7 +7,7 @@
7
7
  module Async
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.8.0"
10
+ VERSION = "0.9.0"
11
11
  end
12
12
  end
13
13
 
data/lib/async/grpc.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require_relative "grpc/version"
7
8
  require_relative "grpc/error"
data/license.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # MIT License
2
2
 
3
3
  Copyright, 2025-2026, by Samuel Williams.
4
+ Copyright, 2026, by Alex Watt.
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -24,6 +24,10 @@ Please see the [project documentation](https://socketry.github.io/async-grpc/) f
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/async-grpc/releases/index) for all releases.
26
26
 
27
+ ### v0.9.0
28
+
29
+ - Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
30
+
27
31
  ### v0.8.0
28
32
 
29
33
  - Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
@@ -70,26 +74,26 @@ Please see the [project releases](https://socketry.github.io/async-grpc/releases
70
74
 
71
75
  We welcome contributions to this project.
72
76
 
73
- 1. Fork it.
77
+ 1. Fork the repository.
74
78
  2. Create your feature branch (`git checkout -b my-new-feature`).
75
- 3. Commit your changes (`git commit -am 'Add some feature'`).
79
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
76
80
  4. Push to the branch (`git push origin my-new-feature`).
77
- 5. Create new Pull Request.
81
+ 5. Create a new pull request.
78
82
 
79
83
  ### Running Tests
80
84
 
81
85
  To run the test suite:
82
86
 
83
- ``` shell
84
- bundle exec sus
87
+ ``` bash
88
+ $ bundle exec sus
85
89
  ```
86
90
 
87
91
  ### Making Releases
88
92
 
89
93
  To make a new release:
90
94
 
91
- ``` shell
92
- bundle exec bake gem:release:patch # or minor or major
95
+ ``` bash
96
+ $ bundle exec bake gem:release:patch # or minor or major
93
97
  ```
94
98
 
95
99
  ### Developer Certificate of Origin
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.9.0
4
+
5
+ - Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
6
+
3
7
  ## v0.8.0
4
8
 
5
9
  - Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
+ - Alex Watt
8
9
  bindir: bin
9
10
  cert_chain:
10
11
  - |
@@ -72,14 +73,14 @@ dependencies:
72
73
  requirements:
73
74
  - - "~>"
74
75
  - !ruby/object:Gem::Version
75
- version: 0.12.0
76
+ version: 0.14.0
76
77
  type: :runtime
77
78
  prerelease: false
78
79
  version_requirements: !ruby/object:Gem::Requirement
79
80
  requirements:
80
81
  - - "~>"
81
82
  - !ruby/object:Gem::Version
82
- version: 0.12.0
83
+ version: 0.14.0
83
84
  - !ruby/object:Gem::Dependency
84
85
  name: protocol-http
85
86
  requirement: !ruby/object:Gem::Requirement
@@ -116,6 +117,8 @@ homepage: https://github.com/socketry/async-grpc
116
117
  licenses:
117
118
  - MIT
118
119
  metadata:
120
+ bug_tracker_uri: https://github.com/socketry/async-grpc/issues
121
+ changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
119
122
  documentation_uri: https://socketry.github.io/async-grpc/
120
123
  source_code_uri: https://github.com/socketry/async-grpc.git
121
124
  rdoc_options: []
metadata.gz.sig CHANGED
Binary file