restate-sdk 0.8.0-aarch64-linux → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d6b44b67a72794c5def96163fbc7a1a134180ddca5477083468a2d88720b756
4
- data.tar.gz: 36032a61a77ba21adde9b2cf2aede6456812fc4a40ec98bb082cc5e2a5676ae3
3
+ metadata.gz: 80a62b147724910e3329f4263e61a290ccc55e6f0c097ecc0fad4ece63ef2407
4
+ data.tar.gz: 33661eccbb66c39e1ca6c57ea735e4ef74fa79a712e05136c7d0624720484ba9
5
5
  SHA512:
6
- metadata.gz: ec7cef72a64b0158f9a6bec982d6ec68718133ad527b5fbc0e9d30969bad539cbb5e4427eb98877d42c6a947fd5bcae080ed235097a31c0cf119e648f12fc03c
7
- data.tar.gz: d7c3432a6a02c9442d56b164d1de390899683bc20b2775392cff1515fbc234bb84d36515c67a23926c4945386e9cf481dbaead5ed7a8a6f73f91959a4496dd7b
6
+ metadata.gz: e0b1fec80f09851613ccb2c4c04ec677566f845b47e51ae5a248d001e4fa9400ed096b574f475551ac3c29e9ca63775a27be799e9876dc2c49a5c4a45e06a466
7
+ data.tar.gz: 4bbacd32b9999dfdec05918bf7eddb191a2e472b20c977dbab6b898c2bf8b2a6e8600649b2a6bc86fd8072368deab819e96e7b64c642dd2d17434be6084845d2
data/Cargo.lock CHANGED
@@ -31,16 +31,14 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
31
31
 
32
32
  [[package]]
33
33
  name = "bindgen"
34
- version = "0.69.5"
34
+ version = "0.72.1"
35
35
  source = "registry+https://github.com/rust-lang/crates.io-index"
36
- checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
36
+ checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
37
37
  dependencies = [
38
38
  "bitflags",
39
39
  "cexpr",
40
40
  "clang-sys",
41
41
  "itertools 0.12.1",
42
- "lazy_static",
43
- "lazycell",
44
42
  "proc-macro2",
45
43
  "quote",
46
44
  "regex",
@@ -278,12 +276,6 @@ version = "1.5.0"
278
276
  source = "registry+https://github.com/rust-lang/crates.io-index"
279
277
  checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
280
278
 
281
- [[package]]
282
- name = "lazycell"
283
- version = "1.3.0"
284
- source = "registry+https://github.com/rust-lang/crates.io-index"
285
- checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
286
-
287
279
  [[package]]
288
280
  name = "libc"
289
281
  version = "0.2.183"
@@ -489,18 +481,18 @@ dependencies = [
489
481
 
490
482
  [[package]]
491
483
  name = "rb-sys"
492
- version = "0.9.124"
484
+ version = "0.9.128"
493
485
  source = "registry+https://github.com/rust-lang/crates.io-index"
494
- checksum = "c85c4188462601e2aa1469def389c17228566f82ea72f137ed096f21591bc489"
486
+ checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
495
487
  dependencies = [
496
488
  "rb-sys-build",
497
489
  ]
498
490
 
499
491
  [[package]]
500
492
  name = "rb-sys-build"
501
- version = "0.9.124"
493
+ version = "0.9.128"
502
494
  source = "registry+https://github.com/rust-lang/crates.io-index"
503
- checksum = "568068db4102230882e6d4ae8de6632e224ca75fe5970f6e026a04e91ed635d3"
495
+ checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
504
496
  dependencies = [
505
497
  "bindgen",
506
498
  "lazy_static",
@@ -569,7 +561,7 @@ dependencies = [
569
561
 
570
562
  [[package]]
571
563
  name = "restate_internal"
572
- version = "0.8.0"
564
+ version = "0.10.0"
573
565
  dependencies = [
574
566
  "magnus",
575
567
  "rb-sys",
@@ -593,9 +585,9 @@ dependencies = [
593
585
 
594
586
  [[package]]
595
587
  name = "rustc-hash"
596
- version = "1.1.0"
588
+ version = "2.1.2"
597
589
  source = "registry+https://github.com/rust-lang/crates.io-index"
598
- checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
590
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
599
591
 
600
592
  [[package]]
601
593
  name = "rustversion"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "restate_internal"
3
- version = "0.8.0"
3
+ version = "0.10.0"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
Binary file
Binary file
Binary file
@@ -81,6 +81,33 @@ module Restate
81
81
  post_admin("/restate/invocations/#{invocation_id}/kill", nil)
82
82
  end
83
83
 
84
+ # ── Introspection queries ──
85
+
86
+ # Execute a SQL query against Restate's introspection API (DataFusion).
87
+ # The admin API exposes system tables (sys_invocation, sys_journal, state,
88
+ # etc.) that can be queried with standard SQL.
89
+ #
90
+ # @param sql [String] a SQL query string
91
+ # @return [Array<Hash>] rows returned by the query
92
+ #
93
+ # @example
94
+ # client.execute_query("SELECT id, status FROM sys_invocation LIMIT 10")
95
+ def execute_query(sql) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
96
+ uri = URI("#{@admin_url}/query")
97
+ request = Net::HTTP::Post.new(uri)
98
+ request['Content-Type'] = 'application/json'
99
+ request['Accept'] = 'application/json'
100
+ @admin_headers.each { |k, v| request[k] = v }
101
+ request.body = JSON.generate({ query: sql })
102
+ response = Net::HTTP.start(uri.hostname, uri.port, # steep:ignore ArgumentTypeMismatch
103
+ use_ssl: uri.scheme == 'https',
104
+ open_timeout: 5,
105
+ read_timeout: 30) { |http| http.request(request) }
106
+ Kernel.raise "Restate query error: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
107
+ body = response.body
108
+ body && !body.empty? ? (JSON.parse(body)['rows'] || []) : []
109
+ end
110
+
84
111
  private
85
112
 
86
113
  def resolve_name(service)
@@ -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
@@ -0,0 +1,127 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'active_record'
5
+
6
+ module Restate
7
+ # Arel table references for Restate's SQL introspection tables.
8
+ #
9
+ # Restate exposes a DataFusion-powered SQL endpoint at +/query+ on the admin
10
+ # API. These tables let you build queries using Arel's composable, type-safe
11
+ # predicate API — the same one ActiveRecord uses under the hood.
12
+ #
13
+ # @example Query running invocations for a service
14
+ # i = Restate::Sys::Invocation
15
+ # query = i.project(i[:id], i[:status], i[:created_at])
16
+ # .where(i[:target_service_name].eq("MyService"))
17
+ # .where(i[:status].eq("running"))
18
+ # .order(i[:created_at].desc)
19
+ # .take(50)
20
+ # Restate.query(query)
21
+ #
22
+ # @example Join invocations with their journal input
23
+ # i = Restate::Sys::Invocation
24
+ # j = Restate::Sys::Journal
25
+ # query = i.project(i[:id], i[:target], i[:status], j[:entry_json])
26
+ # .join(j, Arel::Nodes::OuterJoin)
27
+ # .on(j[:id].eq(i[:id]).and(j[:index].eq(0)))
28
+ # .where(i[:target_service_name].eq("CrawlPipeline"))
29
+ # .order(i[:created_at].desc)
30
+ # .take(20)
31
+ # Restate.query(query)
32
+ #
33
+ # @example Query virtual object state
34
+ # s = Restate::Sys::State
35
+ # query = s.project(s[:service_name], s[:service_key], s[:key], s[:value_utf8])
36
+ # .where(s[:service_name].eq("Counter"))
37
+ # Restate.query(query)
38
+ module Sys
39
+ Invocation = Arel::Table.new(:sys_invocation)
40
+ Journal = Arel::Table.new(:sys_journal)
41
+ JournalEvents = Arel::Table.new(:sys_journal_events)
42
+ Inbox = Arel::Table.new(:sys_inbox)
43
+ KeyedStatus = Arel::Table.new(:sys_keyed_service_status)
44
+ Service = Arel::Table.new(:sys_service)
45
+ Deployment = Arel::Table.new(:sys_deployment)
46
+ Idempotency = Arel::Table.new(:sys_idempotency)
47
+ Promise = Arel::Table.new(:sys_promise)
48
+ State = Arel::Table.new(:state)
49
+ end
50
+
51
+ # Minimal quoting adapter for Arel's ToSql visitor. Emits ANSI SQL
52
+ # (double-quoted identifiers, single-quoted strings) which DataFusion expects.
53
+ # Allows Arel query generation without an ActiveRecord database connection.
54
+ #
55
+ # @!visibility private
56
+ class DataFusionQuoting
57
+ def quote_table_name(name)
58
+ "\"#{name}\""
59
+ end
60
+
61
+ def quote_column_name(name)
62
+ "\"#{name}\""
63
+ end
64
+
65
+ def quote(value)
66
+ case value
67
+ when String then "'#{value.gsub("'", "''")}'"
68
+ when nil then 'NULL'
69
+ when true then 'TRUE'
70
+ when false then 'FALSE'
71
+ else value.to_s
72
+ end
73
+ end
74
+
75
+ def schema_cache
76
+ self
77
+ end
78
+
79
+ def columns_hash(_table)
80
+ {}
81
+ end
82
+
83
+ def data_source_exists?(_table)
84
+ true
85
+ end
86
+ end
87
+
88
+ # Arel visitor that generates ANSI SQL compatible with DataFusion.
89
+ # Uses DataFusionQuoting for identifier/value quoting without requiring
90
+ # a live database connection.
91
+ #
92
+ # @!visibility private
93
+ class DataFusionVisitor < Arel::Visitors::ToSql
94
+ def initialize
95
+ super(DataFusionQuoting.new)
96
+ end
97
+ end
98
+
99
+ class << self
100
+ # Convert an Arel AST to a SQL string without requiring an AR connection.
101
+ # Uses a standalone visitor that emits ANSI SQL (DataFusion-compatible).
102
+ #
103
+ # @param arel [Arel::SelectManager] an Arel query
104
+ # @return [String] SQL string
105
+ def arel_to_sql(arel)
106
+ collector = Arel::Collectors::SQLString.new
107
+ DataFusionVisitor.new.accept(arel.ast, collector).value
108
+ end
109
+
110
+ # Execute an Arel query or raw SQL string against the Restate admin
111
+ # introspection API. Returns an array of row hashes.
112
+ #
113
+ # @param arel_or_sql [Arel::SelectManager, String] an Arel query or raw SQL
114
+ # @return [Array<Hash>] rows returned by Restate
115
+ #
116
+ # @example With Arel
117
+ # i = Restate::Sys::Invocation
118
+ # Restate.query(i.project(Arel.star).take(10))
119
+ #
120
+ # @example With raw SQL
121
+ # Restate.query("SELECT id, status FROM sys_invocation LIMIT 10")
122
+ def query(arel_or_sql)
123
+ sql = arel_or_sql.respond_to?(:ast) ? arel_to_sql(arel_or_sql) : arel_or_sql.to_s
124
+ client.execute_query(sql)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,17 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Restate
5
+ # Rails integration for the Restate SDK. Automatically loads the
6
+ # introspection module which provides Arel-based query support for
7
+ # Restate's SQL introspection API (powered by DataFusion).
8
+ #
9
+ # When Rails is present, +Restate::Sys+ table constants become available
10
+ # for building type-safe, composable queries against system tables like
11
+ # +sys_invocation+, +sys_journal+, and +state+.
12
+ class Railtie < Rails::Railtie
13
+ initializer 'restate.introspection' do
14
+ require_relative 'introspection'
15
+ end
16
+ end
17
+ 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.10.0'
6
6
  end
data/lib/restate.rb CHANGED
@@ -18,6 +18,7 @@ require_relative 'restate/endpoint'
18
18
  require_relative 'restate/service_proxy'
19
19
  require_relative 'restate/config'
20
20
  require_relative 'restate/client'
21
+ require_relative 'restate/railtie' if defined?(Rails::Railtie)
21
22
 
22
23
  # Restate Ruby SDK — build resilient applications with durable execution.
23
24
  #
data/sig/restate.rbs CHANGED
@@ -8,6 +8,8 @@ module Restate
8
8
  def self.configure: () { (Config) -> void } -> void
9
9
  def self.config: () -> Config
10
10
  def self.client: () -> Client
11
+ def self.query: (untyped arel_or_sql) -> Array[Hash[String, untyped]]
12
+ def self.arel_to_sql: (untyped arel) -> String
11
13
 
12
14
  # ── Durable execution ──
13
15
 
@@ -118,6 +120,7 @@ module Restate
118
120
  def reject_awakeable: (String awakeable_id, String message, ?code: Integer) -> void
119
121
  def cancel_invocation: (String invocation_id) -> void
120
122
  def kill_invocation: (String invocation_id) -> void
123
+ def execute_query: (String sql) -> Array[Hash[String, untyped]]
121
124
 
122
125
  private
123
126
 
@@ -138,13 +141,19 @@ module Restate
138
141
  attr_reader identity_keys: Array[String]
139
142
  attr_accessor protocol: String?
140
143
  attr_reader middleware: Array[untyped]
144
+ attr_reader outbound_middleware: Array[untyped]
141
145
  def initialize: () -> void
142
146
  def bind: (*untyped svcs) -> self
143
147
  def streaming_protocol: () -> self
144
148
  def request_response_protocol: () -> self
145
149
  def identity_key: (String key) -> self
146
150
  def use: (untyped klass, *untyped args, **untyped kwargs) -> self
151
+ def use_outbound: (untyped klass, *untyped args, **untyped kwargs) -> self
147
152
  def app: () -> untyped
153
+
154
+ private
155
+
156
+ def instantiate_middleware: (untyped klass, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
148
157
  end
149
158
 
150
159
  # ── Service proxies ──
metadata CHANGED
@@ -1,14 +1,14 @@
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.10.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Restate Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-23 00:00:00.000000000 Z
11
+ date: 2026-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -54,7 +54,6 @@ files:
54
54
  - ext/restate_internal/extconf.rb
55
55
  - ext/restate_internal/src/lib.rs
56
56
  - lib/restate.rb
57
- - lib/restate/3.2/restate_internal.so
58
57
  - lib/restate/3.3/restate_internal.so
59
58
  - lib/restate/3.4/restate_internal.so
60
59
  - lib/restate/4.0/restate_internal.so
@@ -66,6 +65,8 @@ files:
66
65
  - lib/restate/endpoint.rb
67
66
  - lib/restate/errors.rb
68
67
  - lib/restate/handler.rb
68
+ - lib/restate/introspection.rb
69
+ - lib/restate/railtie.rb
69
70
  - lib/restate/serde.rb
70
71
  - lib/restate/server.rb
71
72
  - lib/restate/server_context.rb
@@ -91,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
92
  requirements:
92
93
  - - ">="
93
94
  - !ruby/object:Gem::Version
94
- version: '3.2'
95
+ version: '3.3'
95
96
  - - "<"
96
97
  - !ruby/object:Gem::Version
97
98
  version: 4.1.dev
Binary file