restate-sdk 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: cc64f400fa1b48565474c9cc26c8bde6742e58b0c02f5090c0f633e16fd2b354
4
- data.tar.gz: 7d72ad638b4dd6742c149d5314e8c67de919d198362d4a29e65d78da24146898
3
+ metadata.gz: 0fdc1d0db30055117693522a2f51fc194ed403a53b6069b2a69181c859b0e9f9
4
+ data.tar.gz: c39fde762f884f79335330428ba8e9d2bc07b86ba77b12d268db1cd727660cab
5
5
  SHA512:
6
- metadata.gz: 6180be88c64766e8537a59ce16c22ff591d9a0b6620301668b0c7759b981f2344a9d04f9e6d896b08a569168cd517e29e9202c69947f1311410911c1c2190f85
7
- data.tar.gz: bfaa1a28cbfdbeafa95a10a428e7e0b7f17ebc033e0c28659ffeec059c980c4f1635827f40a799e517ccf31c6d143bd342fb6fdfa4b70f6f8dd9709d383af7a9
6
+ metadata.gz: 2f460e546e7fb00ca7b6319e55374a52a944c58fcd3b0f0f399a62342e12d420280934473d0360d73d26be7dfd525cec7c4bfa831e198d90cb088ff6855351a1
7
+ data.tar.gz: 14361197f0c9bf7b798637d4a4e617d8438fdc46c433670eca069de100772e2126f8a0a41b27f0a7a283227f452c056aef8fce6efdaf0d60e5b86ef88a4fd1eb
data/Cargo.lock CHANGED
@@ -569,7 +569,7 @@ dependencies = [
569
569
 
570
570
  [[package]]
571
571
  name = "restate_internal"
572
- version = "0.8.0"
572
+ version = "0.9.0"
573
573
  dependencies = [
574
574
  "magnus",
575
575
  "rb-sys",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "restate_internal"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -4,7 +4,7 @@
4
4
  module Restate
5
5
  # Container for registered services. Bind services here, then create the Rack app.
6
6
  class Endpoint
7
- attr_reader :services, :identity_keys, :middleware
7
+ attr_reader :services, :identity_keys, :middleware, :outbound_middleware
8
8
 
9
9
  attr_accessor :protocol
10
10
 
@@ -13,6 +13,7 @@ module Restate
13
13
  @protocol = nil
14
14
  @identity_keys = []
15
15
  @middleware = []
16
+ @outbound_middleware = []
16
17
  end
17
18
 
18
19
  # Bind one or more services to this endpoint.
@@ -48,29 +49,25 @@ module Restate
48
49
  self
49
50
  end
50
51
 
51
- # Add handler-level middleware.
52
+ # Add inbound (server) middleware.
52
53
  #
53
- # Middleware wraps every handler invocation with access to the handler metadata
54
- # and context. Use it for tracing, metrics, logging, error reporting, etc.
54
+ # Inbound middleware wraps every handler invocation, like
55
+ # {https://github.com/sidekiq/sidekiq/wiki/Middleware Sidekiq server middleware}.
55
56
  #
56
57
  # A middleware is a class whose instances respond to +call(handler, ctx)+.
57
58
  # Use +yield+ inside +call+ to invoke the next middleware or the handler.
58
59
  # The return value of +yield+ is the handler's return value.
59
60
  #
60
- # This follows the same pattern as {https://github.com/sidekiq/sidekiq/wiki/Middleware Sidekiq middleware}.
61
- #
62
61
  # @example OpenTelemetry tracing
63
- # class OpenTelemetryMiddleware
62
+ # class TracingMiddleware
64
63
  # def call(handler, ctx)
65
- # tracer.in_span(handler.name, attributes: {
66
- # 'restate.service' => handler.service_tag.name,
67
- # 'restate.invocation_id' => ctx.request.id
68
- # }) do
69
- # yield
64
+ # extracted = OpenTelemetry.propagation.extract(ctx.request.headers)
65
+ # OpenTelemetry::Context.with_current(extracted) do
66
+ # tracer.in_span(handler.name) { yield }
70
67
  # end
71
68
  # end
72
69
  # end
73
- # endpoint.use(OpenTelemetryMiddleware)
70
+ # endpoint.use(TracingMiddleware)
74
71
  #
75
72
  # @example Metrics
76
73
  # class MetricsMiddleware
@@ -84,30 +81,53 @@ module Restate
84
81
  # end
85
82
  # endpoint.use(MetricsMiddleware)
86
83
  #
87
- # @example Middleware with configuration
88
- # class AuthMiddleware
89
- # def initialize(api_key:)
90
- # @api_key = api_key
84
+ # @param klass [Class] middleware class (will be instantiated by the SDK)
85
+ # @param args [Array] positional arguments for the middleware constructor
86
+ # @param kwargs [Hash] keyword arguments for the middleware constructor
87
+ # @return [self]
88
+ def use(klass, *args, **kwargs)
89
+ @middleware << instantiate_middleware(klass, args, kwargs)
90
+ self
91
+ end
92
+
93
+ # Add outbound (client) middleware.
94
+ #
95
+ # Outbound middleware wraps every outgoing service call and send, like
96
+ # {https://github.com/sidekiq/sidekiq/wiki/Middleware Sidekiq client middleware}.
97
+ #
98
+ # A middleware is a class whose instances respond to +call(service, handler, headers)+.
99
+ # The +headers+ hash is mutable — modify it to attach headers to the outgoing
100
+ # request. Use +yield+ to continue the chain.
101
+ #
102
+ # Note: Restate automatically propagates inbound headers to outbound calls.
103
+ # Outbound middleware is for injecting *new* headers that aren't on the
104
+ # original request (e.g., tenant IDs from fiber-local storage, authorization
105
+ # tokens for specific target services).
106
+ #
107
+ # @example Propagate tenant ID to all outgoing calls
108
+ # class TenantOutboundMiddleware
109
+ # def call(_service, _handler, headers)
110
+ # headers['x-tenant-id'] = Thread.current[:tenant_id]
111
+ # yield
91
112
  # end
113
+ # end
114
+ # endpoint.use_outbound(TenantOutboundMiddleware)
92
115
  #
93
- # def call(handler, ctx)
94
- # raise Restate::TerminalError.new('unauthorized', status_code: 401) unless valid?(ctx)
116
+ # @example Log all outgoing calls
117
+ # class OutboundLogger
118
+ # def call(service, handler, headers)
119
+ # logger.info("Calling #{service}/#{handler}")
95
120
  # yield
96
121
  # end
97
122
  # end
98
- # endpoint.use(AuthMiddleware, api_key: 'secret')
123
+ # endpoint.use_outbound(OutboundLogger)
99
124
  #
100
125
  # @param klass [Class] middleware class (will be instantiated by the SDK)
101
126
  # @param args [Array] positional arguments for the middleware constructor
102
127
  # @param kwargs [Hash] keyword arguments for the middleware constructor
103
128
  # @return [self]
104
- def use(klass, *args, **kwargs)
105
- instance = if kwargs.empty?
106
- klass.new(*args)
107
- else
108
- klass.new(*args, **kwargs)
109
- end
110
- @middleware << instance
129
+ def use_outbound(klass, *args, **kwargs)
130
+ @outbound_middleware << instantiate_middleware(klass, args, kwargs)
111
131
  self
112
132
  end
113
133
 
@@ -116,5 +136,15 @@ module Restate
116
136
  require_relative 'server'
117
137
  Server.new(self)
118
138
  end
139
+
140
+ private
141
+
142
+ def instantiate_middleware(klass, args, kwargs)
143
+ if kwargs.empty?
144
+ klass.new(*args)
145
+ else
146
+ klass.new(*args, **kwargs)
147
+ end
148
+ end
119
149
  end
120
150
  end
@@ -193,7 +193,8 @@ module Restate
193
193
  invocation: invocation,
194
194
  send_output: send_output,
195
195
  input_queue: input_queue,
196
- middleware: @endpoint.middleware
196
+ middleware: @endpoint.middleware,
197
+ outbound_middleware: @endpoint.outbound_middleware
197
198
  )
198
199
 
199
200
  # Spawn the handler as an async task so the response body can stream
@@ -23,7 +23,8 @@ module Restate
23
23
 
24
24
  attr_reader :vm, :invocation
25
25
 
26
- def initialize(vm:, handler:, invocation:, send_output:, input_queue:, middleware: [])
26
+ def initialize(vm:, handler:, invocation:, send_output:, input_queue:, middleware: [],
27
+ outbound_middleware: [])
27
28
  @vm = vm
28
29
  @handler = handler
29
30
  @invocation = invocation
@@ -32,6 +33,7 @@ module Restate
32
33
  @run_coros_to_execute = {}
33
34
  @attempt_finished_event = AttemptFinishedEvent.new
34
35
  @middleware = middleware
36
+ @outbound_middleware = outbound_middleware
35
37
  end
36
38
 
37
39
  # ── Main entry point ──
@@ -204,12 +206,14 @@ module Restate
204
206
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
205
207
  out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
206
208
  parameter = in_serde.serialize(arg)
207
- call_handle = @vm.sys_call(
208
- service: svc_name, handler: handler_name, parameter: parameter,
209
- key: key, idempotency_key: idempotency_key, headers: headers
210
- )
211
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
212
- output_serde: out_serde)
209
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
210
+ call_handle = @vm.sys_call(
211
+ service: svc_name, handler: handler_name, parameter: parameter,
212
+ key: key, idempotency_key: idempotency_key, headers: hdrs
213
+ )
214
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
215
+ output_serde: out_serde)
216
+ end
213
217
  end
214
218
 
215
219
  # Sends a one-way invocation to a Restate service handler (fire-and-forget).
@@ -219,11 +223,13 @@ module Restate
219
223
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
220
224
  parameter = in_serde.serialize(arg)
221
225
  delay_ms = delay ? (delay * 1000).to_i : nil
222
- invocation_id_handle = @vm.sys_send(
223
- service: svc_name, handler: handler_name, parameter: parameter,
224
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
225
- )
226
- SendHandle.new(self, invocation_id_handle)
226
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
227
+ invocation_id_handle = @vm.sys_send(
228
+ service: svc_name, handler: handler_name, parameter: parameter,
229
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
230
+ )
231
+ SendHandle.new(self, invocation_id_handle)
232
+ end
227
233
  end
228
234
 
229
235
  # Durably calls a handler on a Restate virtual object, keyed by +key+.
@@ -233,12 +239,14 @@ module Restate
233
239
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
234
240
  out_serde = resolve_serde(output_serde, handler_meta, :output_serde)
235
241
  parameter = in_serde.serialize(arg)
236
- call_handle = @vm.sys_call(
237
- service: svc_name, handler: handler_name, parameter: parameter,
238
- key: key, idempotency_key: idempotency_key, headers: headers
239
- )
240
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
241
- output_serde: out_serde)
242
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
243
+ call_handle = @vm.sys_call(
244
+ service: svc_name, handler: handler_name, parameter: parameter,
245
+ key: key, idempotency_key: idempotency_key, headers: hdrs
246
+ )
247
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
248
+ output_serde: out_serde)
249
+ end
242
250
  end
243
251
 
244
252
  # Sends a one-way invocation to a Restate virtual object handler (fire-and-forget).
@@ -248,11 +256,13 @@ module Restate
248
256
  in_serde = resolve_serde(input_serde, handler_meta, :input_serde)
249
257
  parameter = in_serde.serialize(arg)
250
258
  delay_ms = delay ? (delay * 1000).to_i : nil
251
- invocation_id_handle = @vm.sys_send(
252
- service: svc_name, handler: handler_name, parameter: parameter,
253
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
254
- )
255
- SendHandle.new(self, invocation_id_handle)
259
+ with_outbound_middleware(svc_name, handler_name, headers) do |hdrs|
260
+ invocation_id_handle = @vm.sys_send(
261
+ service: svc_name, handler: handler_name, parameter: parameter,
262
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
263
+ )
264
+ SendHandle.new(self, invocation_id_handle)
265
+ end
256
266
  end
257
267
 
258
268
  # Durably calls a handler on a Restate workflow, keyed by +key+.
@@ -332,22 +342,26 @@ module Restate
332
342
 
333
343
  # Durably calls a handler using raw bytes (no serialization). Useful for proxying.
334
344
  def generic_call(service, handler, arg, key: nil, idempotency_key: nil, headers: nil)
335
- call_handle = @vm.sys_call(
336
- service: service, handler: handler, parameter: arg,
337
- key: key, idempotency_key: idempotency_key, headers: headers
338
- )
339
- DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
340
- output_serde: nil)
345
+ with_outbound_middleware(service, handler, headers) do |hdrs|
346
+ call_handle = @vm.sys_call(
347
+ service: service, handler: handler, parameter: arg,
348
+ key: key, idempotency_key: idempotency_key, headers: hdrs
349
+ )
350
+ DurableCallFuture.new(self, call_handle.result_handle, call_handle.invocation_id_handle,
351
+ output_serde: nil)
352
+ end
341
353
  end
342
354
 
343
355
  # Sends a one-way invocation using raw bytes (no serialization). Useful for proxying.
344
356
  def generic_send(service, handler, arg, key: nil, delay: nil, idempotency_key: nil, headers: nil)
345
357
  delay_ms = delay ? (delay * 1000).to_i : nil
346
- invocation_id_handle = @vm.sys_send(
347
- service: service, handler: handler, parameter: arg,
348
- key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: headers
349
- )
350
- SendHandle.new(self, invocation_id_handle)
358
+ with_outbound_middleware(service, handler, headers) do |hdrs|
359
+ invocation_id_handle = @vm.sys_send(
360
+ service: service, handler: handler, parameter: arg,
361
+ key: key, delay: delay_ms, idempotency_key: idempotency_key, headers: hdrs
362
+ )
363
+ SendHandle.new(self, invocation_id_handle)
364
+ end
351
365
  end
352
366
 
353
367
  # ── Request metadata ──
@@ -453,6 +467,25 @@ module Restate
453
467
  end
454
468
  end
455
469
 
470
+ # ── Outbound middleware ──
471
+
472
+ # Runs outbound middleware chain (Sidekiq client middleware pattern).
473
+ # Each middleware gets +call(service, handler, headers)+ and must +yield+
474
+ # to continue the chain. The block at the end performs the actual VM call.
475
+ def with_outbound_middleware(service, handler, headers, &action)
476
+ if @outbound_middleware.empty?
477
+ action.call(headers)
478
+ else
479
+ h = headers || {}
480
+ chain = ->(hdrs) { action.call(hdrs) }
481
+ @outbound_middleware.reverse_each do |mw|
482
+ prev = chain
483
+ chain = ->(hdrs) { mw.call(service, handler, hdrs) { prev.call(hdrs) } }
484
+ end
485
+ chain.call(h)
486
+ end
487
+ end
488
+
456
489
  # ── Call target resolution ──
457
490
 
458
491
  # Resolves a service+handler pair from class/symbol or string/string.
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.0'
6
6
  end
data/sig/restate.rbs CHANGED
@@ -138,13 +138,19 @@ module Restate
138
138
  attr_reader identity_keys: Array[String]
139
139
  attr_accessor protocol: String?
140
140
  attr_reader middleware: Array[untyped]
141
+ attr_reader outbound_middleware: Array[untyped]
141
142
  def initialize: () -> void
142
143
  def bind: (*untyped svcs) -> self
143
144
  def streaming_protocol: () -> self
144
145
  def request_response_protocol: () -> self
145
146
  def identity_key: (String key) -> self
146
147
  def use: (untyped klass, *untyped args, **untyped kwargs) -> self
148
+ def use_outbound: (untyped klass, *untyped args, **untyped kwargs) -> self
147
149
  def app: () -> untyped
150
+
151
+ private
152
+
153
+ def instantiate_middleware: (untyped klass, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
148
154
  end
149
155
 
150
156
  # ── Service proxies ──
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restate-sdk
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
  - Restate Developers