restate-sdk 0.7.0-aarch64-linux → 0.9.0-aarch64-linux

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.
@@ -5,24 +5,19 @@ module Restate
5
5
  # A durable future wrapping a VM handle. Lazily resolves on first +await+ and caches the result.
6
6
  # Returned by +ctx.run+ and +ctx.sleep+.
7
7
  class DurableFuture
8
- extend T::Sig
9
-
10
- sig { returns(Integer) }
11
8
  attr_reader :handle
12
9
 
13
- sig { params(ctx: ServerContext, handle: Integer, serde: T.untyped).void }
14
10
  def initialize(ctx, handle, serde: nil)
15
- @ctx = T.let(ctx, ServerContext)
16
- @handle = T.let(handle, Integer)
17
- @serde = T.let(serde, T.untyped)
18
- @resolved = T.let(false, T::Boolean)
19
- @value = T.let(nil, T.untyped)
11
+ @ctx = ctx
12
+ @handle = handle
13
+ @serde = serde
14
+ @resolved = false
15
+ @value = nil
20
16
  end
21
17
 
22
18
  # Block until the result is available and return it. Caches across calls.
23
19
  #
24
20
  # @return [Object] the deserialized result
25
- sig { returns(T.untyped) }
26
21
  def await
27
22
  unless @resolved
28
23
  raw = @ctx.resolve_handle(@handle)
@@ -35,7 +30,6 @@ module Restate
35
30
  # Check whether the future has completed (non-blocking).
36
31
  #
37
32
  # @return [Boolean]
38
- sig { returns(T::Boolean) }
39
33
  def completed?
40
34
  @resolved || @ctx.completed?(@handle)
41
35
  end
@@ -45,26 +39,15 @@ module Restate
45
39
  # Adds +invocation_id+ and +cancel+ on top of DurableFuture.
46
40
  # Returned by +ctx.service_call+, +ctx.object_call+, +ctx.workflow_call+.
47
41
  class DurableCallFuture < DurableFuture
48
- extend T::Sig
49
-
50
- sig do
51
- params(
52
- ctx: ServerContext,
53
- result_handle: Integer,
54
- invocation_id_handle: Integer,
55
- output_serde: T.untyped
56
- ).void
57
- end
58
42
  def initialize(ctx, result_handle, invocation_id_handle, output_serde:)
59
43
  super(ctx, result_handle)
60
- @invocation_id_handle = T.let(invocation_id_handle, Integer)
61
- @output_serde = T.let(output_serde, T.untyped)
62
- @invocation_id_resolved = T.let(false, T::Boolean)
63
- @invocation_id_value = T.let(nil, T.untyped)
44
+ @invocation_id_handle = invocation_id_handle
45
+ @output_serde = output_serde
46
+ @invocation_id_resolved = false
47
+ @invocation_id_value = nil
64
48
  end
65
49
 
66
50
  # Block until the result is available and return it. Deserializes via +output_serde+.
67
- sig { returns(T.untyped) }
68
51
  def await
69
52
  unless @resolved
70
53
  raw = @ctx.resolve_handle(@handle)
@@ -81,17 +64,15 @@ module Restate
81
64
  # Returns the invocation ID of the remote call. Lazily resolved.
82
65
  #
83
66
  # @return [String] the invocation ID
84
- sig { returns(String) }
85
67
  def invocation_id
86
68
  unless @invocation_id_resolved
87
69
  @invocation_id_value = @ctx.resolve_handle(@invocation_id_handle)
88
70
  @invocation_id_resolved = true
89
71
  end
90
- T.must(@invocation_id_value)
72
+ @invocation_id_value
91
73
  end
92
74
 
93
75
  # Cancel the remote invocation.
94
- sig { void }
95
76
  def cancel
96
77
  @ctx.cancel_invocation(invocation_id)
97
78
  end
@@ -100,30 +81,25 @@ module Restate
100
81
  # A handle for fire-and-forget send operations.
101
82
  # Returned by +ctx.service_send+, +ctx.object_send+, +ctx.workflow_send+.
102
83
  class SendHandle
103
- extend T::Sig
104
-
105
- sig { params(ctx: ServerContext, invocation_id_handle: Integer).void }
106
84
  def initialize(ctx, invocation_id_handle)
107
- @ctx = T.let(ctx, ServerContext)
108
- @invocation_id_handle = T.let(invocation_id_handle, Integer)
109
- @invocation_id_resolved = T.let(false, T::Boolean)
110
- @invocation_id_value = T.let(nil, T.untyped)
85
+ @ctx = ctx
86
+ @invocation_id_handle = invocation_id_handle
87
+ @invocation_id_resolved = false
88
+ @invocation_id_value = nil
111
89
  end
112
90
 
113
91
  # Returns the invocation ID of the sent call. Lazily resolved.
114
92
  #
115
93
  # @return [String] the invocation ID
116
- sig { returns(String) }
117
94
  def invocation_id
118
95
  unless @invocation_id_resolved
119
96
  @invocation_id_value = @ctx.resolve_handle(@invocation_id_handle)
120
97
  @invocation_id_resolved = true
121
98
  end
122
- T.must(@invocation_id_value)
99
+ @invocation_id_value
123
100
  end
124
101
 
125
102
  # Cancel the remote invocation.
126
- sig { void }
127
103
  def cancel
128
104
  @ctx.cancel_invocation(invocation_id)
129
105
  end
@@ -4,26 +4,16 @@
4
4
  module Restate
5
5
  # Container for registered services. Bind services here, then create the Rack app.
6
6
  class Endpoint
7
- extend T::Sig
7
+ attr_reader :services, :identity_keys, :middleware, :outbound_middleware
8
8
 
9
- sig { returns(T::Hash[String, T.untyped]) }
10
- attr_reader :services
11
-
12
- sig { returns(T::Array[String]) }
13
- attr_reader :identity_keys
14
-
15
- sig { returns(T.nilable(String)) }
16
9
  attr_accessor :protocol
17
10
 
18
- sig { returns(T::Array[T.untyped]) }
19
- attr_reader :middleware
20
-
21
- sig { void }
22
11
  def initialize
23
- @services = T.let({}, T::Hash[String, T.untyped])
24
- @protocol = T.let(nil, T.nilable(String))
25
- @identity_keys = T.let([], T::Array[String])
26
- @middleware = T.let([], T::Array[T.untyped])
12
+ @services = {}
13
+ @protocol = nil
14
+ @identity_keys = []
15
+ @middleware = []
16
+ @outbound_middleware = []
27
17
  end
28
18
 
29
19
  # Bind one or more services to this endpoint.
@@ -31,7 +21,6 @@ module Restate
31
21
  # @param svcs [Array<Class<Service>, Class<VirtualObject>, Class<Workflow>>] services to bind
32
22
  # @return [self]
33
23
  # @raise [ArgumentError] if a service with the same name is already bound
34
- sig { params(svcs: T.untyped).returns(T.self_type) }
35
24
  def bind(*svcs)
36
25
  svcs.each do |svc|
37
26
  svc_name = svc.service_name
@@ -43,49 +32,42 @@ module Restate
43
32
  end
44
33
 
45
34
  # Force bidirectional streaming protocol.
46
- sig { returns(T.self_type) }
47
35
  def streaming_protocol
48
36
  @protocol = 'bidi'
49
37
  self
50
38
  end
51
39
 
52
40
  # Force request/response protocol.
53
- sig { returns(T.self_type) }
54
41
  def request_response_protocol
55
42
  @protocol = 'request_response'
56
43
  self
57
44
  end
58
45
 
59
46
  # Add an identity key for request verification.
60
- sig { params(key: String).returns(T.self_type) }
61
47
  def identity_key(key)
62
48
  @identity_keys << key
63
49
  self
64
50
  end
65
51
 
66
- # Add handler-level middleware.
52
+ # Add inbound (server) middleware.
67
53
  #
68
- # Middleware wraps every handler invocation with access to the handler metadata
69
- # 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}.
70
56
  #
71
57
  # A middleware is a class whose instances respond to +call(handler, ctx)+.
72
58
  # Use +yield+ inside +call+ to invoke the next middleware or the handler.
73
59
  # The return value of +yield+ is the handler's return value.
74
60
  #
75
- # This follows the same pattern as {https://github.com/sidekiq/sidekiq/wiki/Middleware Sidekiq middleware}.
76
- #
77
61
  # @example OpenTelemetry tracing
78
- # class OpenTelemetryMiddleware
62
+ # class TracingMiddleware
79
63
  # def call(handler, ctx)
80
- # tracer.in_span(handler.name, attributes: {
81
- # 'restate.service' => handler.service_tag.name,
82
- # 'restate.invocation_id' => ctx.request.id
83
- # }) do
84
- # yield
64
+ # extracted = OpenTelemetry.propagation.extract(ctx.request.headers)
65
+ # OpenTelemetry::Context.with_current(extracted) do
66
+ # tracer.in_span(handler.name) { yield }
85
67
  # end
86
68
  # end
87
69
  # end
88
- # endpoint.use(OpenTelemetryMiddleware)
70
+ # endpoint.use(TracingMiddleware)
89
71
  #
90
72
  # @example Metrics
91
73
  # class MetricsMiddleware
@@ -99,39 +81,70 @@ module Restate
99
81
  # end
100
82
  # endpoint.use(MetricsMiddleware)
101
83
  #
102
- # @example Middleware with configuration
103
- # class AuthMiddleware
104
- # def initialize(api_key:)
105
- # @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
106
112
  # end
113
+ # end
114
+ # endpoint.use_outbound(TenantOutboundMiddleware)
107
115
  #
108
- # def call(handler, ctx)
109
- # 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}")
110
120
  # yield
111
121
  # end
112
122
  # end
113
- # endpoint.use(AuthMiddleware, api_key: 'secret')
123
+ # endpoint.use_outbound(OutboundLogger)
114
124
  #
115
125
  # @param klass [Class] middleware class (will be instantiated by the SDK)
116
126
  # @param args [Array] positional arguments for the middleware constructor
117
127
  # @param kwargs [Hash] keyword arguments for the middleware constructor
118
128
  # @return [self]
119
- sig { params(klass: T.untyped, args: T.untyped, kwargs: T.untyped).returns(T.self_type) }
120
- def use(klass, *args, **kwargs)
121
- instance = if kwargs.empty?
122
- klass.new(*args)
123
- else
124
- klass.new(*args, **kwargs)
125
- end
126
- @middleware << instance
129
+ def use_outbound(klass, *args, **kwargs)
130
+ @outbound_middleware << instantiate_middleware(klass, args, kwargs)
127
131
  self
128
132
  end
129
133
 
130
134
  # Build and return the Rack-compatible application.
131
- sig { returns(T.untyped) }
132
135
  def app
133
136
  require_relative 'server'
134
137
  Server.new(self)
135
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
136
149
  end
137
150
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
@@ -7,24 +7,17 @@ module Restate
7
7
  # @example
8
8
  # raise Restate::TerminalError.new('not found', status_code: 404)
9
9
  class TerminalError < StandardError
10
- extend T::Sig
11
-
12
- sig { returns(Integer) }
13
10
  attr_reader :status_code
14
11
 
15
- sig { params(message: String, status_code: Integer).void }
16
12
  def initialize(message = 'Internal Server Error', status_code: 500)
17
13
  super(message)
18
- @status_code = T.let(status_code, Integer)
14
+ @status_code = status_code
19
15
  end
20
16
  end
21
17
 
22
18
  # Internal: raised when the VM suspends execution.
23
19
  # User code should NOT catch this.
24
20
  class SuspendedError < StandardError
25
- extend T::Sig
26
-
27
- sig { void }
28
21
  def initialize
29
22
  super(
30
23
  "Invocation got suspended, Restate will resume this invocation when progress can be made.\n" \
@@ -37,9 +30,6 @@ module Restate
37
30
 
38
31
  # Internal: raised when the VM encounters a retryable error.
39
32
  class InternalError < StandardError
40
- extend T::Sig
41
-
42
- sig { void }
43
33
  def initialize
44
34
  super(
45
35
  "Invocation attempt raised a retryable error.\n" \
@@ -50,9 +40,6 @@ module Restate
50
40
 
51
41
  # Internal: raised when the HTTP connection is lost.
52
42
  class DisconnectedError < StandardError
53
- extend T::Sig
54
-
55
- sig { void }
56
43
  def initialize
57
44
  super('Disconnected. The connection to the restate server was lost. Restate will retry the attempt.')
58
45
  end
@@ -27,39 +27,39 @@ module Restate
27
27
  keyword_init: true
28
28
  )
29
29
 
30
- extend T::Sig
31
-
32
30
  module_function
33
31
 
34
32
  # Invoke a handler with the context and raw input bytes.
35
33
  # The context is passed as the first argument to every handler.
36
34
  # Middleware (if any) wraps the handler call.
37
35
  # Returns raw output bytes.
38
- sig do
39
- params(handler: T.untyped, ctx: T.untyped, in_buffer: String,
40
- middleware: T::Array[T.untyped]).returns(String)
36
+ def invoke_handler(handler:, ctx:, in_buffer:, middleware: [])
37
+ out_arg = if middleware.empty?
38
+ invoke_handler_direct(handler, in_buffer)
39
+ else
40
+ invoke_handler_with_middleware(handler, ctx, in_buffer, middleware)
41
+ end
42
+ handler.handler_io.output_serde.serialize(out_arg)
41
43
  end
42
- def invoke_handler(handler:, ctx:, in_buffer:, middleware: []) # rubocop:disable Metrics/AbcSize
43
- call_handler = Kernel.proc do
44
- if handler.arity == 1
45
- begin
46
- in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
47
- rescue StandardError => e
48
- Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
49
- end
50
- handler.callable.call(in_arg)
51
- else
52
- handler.callable.call
44
+
45
+ def invoke_handler_direct(handler, in_buffer)
46
+ if handler.arity == 1
47
+ begin
48
+ in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
49
+ rescue StandardError => e
50
+ Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
53
51
  end
52
+ handler.callable.call(in_arg)
53
+ else
54
+ handler.callable.call
54
55
  end
56
+ end
55
57
 
56
- # Build the middleware chain so each middleware can use `yield` to call the next.
57
- # Middleware still receives (handler, ctx) for low-level access.
58
+ def invoke_handler_with_middleware(handler, ctx, in_buffer, middleware)
59
+ call_handler = Kernel.proc { invoke_handler_direct(handler, in_buffer) }
58
60
  chain = middleware.reverse.reduce(call_handler) do |nxt, mw|
59
61
  Kernel.proc { mw.call(handler, ctx, &nxt) }
60
62
  end
61
-
62
- out_arg = chain.call
63
- handler.handler_io.output_serde.serialize(out_arg)
63
+ chain.call
64
64
  end
65
65
  end