restate-sdk 0.7.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 +4 -4
- data/Cargo.lock +1 -1
- data/README.md +15 -10
- data/ext/restate_internal/Cargo.toml +1 -1
- data/lib/restate/client.rb +5 -29
- data/lib/restate/config.rb +4 -11
- data/lib/restate/context.rb +13 -128
- data/lib/restate/discovery.rb +6 -15
- data/lib/restate/durable_future.rb +15 -39
- data/lib/restate/endpoint.rb +61 -48
- data/lib/restate/errors.rb +2 -15
- data/lib/restate/handler.rb +21 -21
- data/lib/restate/serde.rb +14 -130
- data/lib/restate/server.rb +12 -27
- data/lib/restate/server_context.rb +111 -276
- data/lib/restate/service.rb +2 -3
- data/lib/restate/service_dsl.rb +8 -8
- data/lib/restate/service_proxy.rb +2 -13
- data/lib/restate/testing.rb +1 -1
- data/lib/restate/version.rb +2 -2
- data/lib/restate/virtual_object.rb +3 -4
- data/lib/restate/vm.rb +9 -74
- data/lib/restate/workflow.rb +3 -4
- data/lib/restate.rb +4 -93
- data/sig/restate.rbs +202 -0
- metadata +3 -18
- data/lib/tapioca/dsl/compilers/restate.rb +0 -115
- data/rbi/restate-sdk.rbi +0 -582
data/lib/restate/serde.rb
CHANGED
|
@@ -4,23 +4,22 @@
|
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
6
|
module Restate
|
|
7
|
+
EMPTY_BYTES = ''.b.freeze
|
|
8
|
+
private_constant :EMPTY_BYTES
|
|
9
|
+
|
|
7
10
|
# JSON serializer/deserializer (default).
|
|
8
11
|
# Converts Ruby objects to JSON byte strings and back.
|
|
9
12
|
module JsonSerde
|
|
10
|
-
extend T::Sig
|
|
11
|
-
|
|
12
13
|
module_function
|
|
13
14
|
|
|
14
15
|
# Serialize a Ruby object to a JSON byte string. Returns empty bytes for nil.
|
|
15
|
-
sig { params(obj: T.untyped).returns(String) }
|
|
16
16
|
def serialize(obj)
|
|
17
|
-
return
|
|
17
|
+
return EMPTY_BYTES if obj.nil?
|
|
18
18
|
|
|
19
19
|
JSON.generate(obj).b
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Deserialize a JSON byte string to a Ruby object. Returns nil for nil or empty input.
|
|
23
|
-
sig { params(buf: T.nilable(String)).returns(T.untyped) }
|
|
24
23
|
def deserialize(buf)
|
|
25
24
|
return nil if buf.nil? || buf.empty?
|
|
26
25
|
|
|
@@ -28,7 +27,6 @@ module Restate
|
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
# Return the JSON Schema for this serde, or nil if unspecified.
|
|
31
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
32
30
|
def json_schema
|
|
33
31
|
nil
|
|
34
32
|
end
|
|
@@ -40,26 +38,21 @@ module Restate
|
|
|
40
38
|
# Pass-through bytes serializer/deserializer.
|
|
41
39
|
# Passes binary data through without transformation.
|
|
42
40
|
module BytesSerde
|
|
43
|
-
extend T::Sig
|
|
44
|
-
|
|
45
41
|
module_function
|
|
46
42
|
|
|
47
43
|
# Serialize an object by returning its binary encoding. Returns empty bytes for nil.
|
|
48
|
-
sig { params(obj: T.untyped).returns(String) }
|
|
49
44
|
def serialize(obj)
|
|
50
|
-
return
|
|
45
|
+
return EMPTY_BYTES if obj.nil?
|
|
51
46
|
|
|
52
47
|
obj.b
|
|
53
48
|
end
|
|
54
49
|
|
|
55
50
|
# Deserialize by returning the raw buffer unchanged.
|
|
56
|
-
sig { params(buf: T.nilable(String)).returns(T.nilable(String)) }
|
|
57
51
|
def deserialize(buf)
|
|
58
52
|
buf
|
|
59
53
|
end
|
|
60
54
|
|
|
61
55
|
# Return the JSON Schema for this serde, or nil if unspecified.
|
|
62
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
63
56
|
def json_schema
|
|
64
57
|
nil
|
|
65
58
|
end
|
|
@@ -67,34 +60,29 @@ module Restate
|
|
|
67
60
|
|
|
68
61
|
# Maps Ruby primitive types to JSON Schema snippets for discovery.
|
|
69
62
|
PRIMITIVE_SCHEMAS = {
|
|
70
|
-
String => { 'type' => 'string' },
|
|
71
|
-
Integer => { 'type' => 'integer' },
|
|
72
|
-
Float => { 'type' => 'number' },
|
|
73
|
-
TrueClass => { 'type' => 'boolean' },
|
|
74
|
-
FalseClass => { 'type' => 'boolean' },
|
|
75
|
-
Array => { 'type' => 'array' },
|
|
76
|
-
Hash => { 'type' => 'object' },
|
|
77
|
-
NilClass => { 'type' => 'null' }
|
|
63
|
+
String => { 'type' => 'string' }.freeze,
|
|
64
|
+
Integer => { 'type' => 'integer' }.freeze,
|
|
65
|
+
Float => { 'type' => 'number' }.freeze,
|
|
66
|
+
TrueClass => { 'type' => 'boolean' }.freeze,
|
|
67
|
+
FalseClass => { 'type' => 'boolean' }.freeze,
|
|
68
|
+
Array => { 'type' => 'array' }.freeze,
|
|
69
|
+
Hash => { 'type' => 'object' }.freeze,
|
|
70
|
+
NilClass => { 'type' => 'null' }.freeze
|
|
78
71
|
}.freeze
|
|
79
72
|
|
|
80
73
|
# Serde resolution utilities: converts a type or serde into a serde object.
|
|
81
74
|
module Serde
|
|
82
|
-
extend T::Sig
|
|
83
|
-
|
|
84
75
|
module_function
|
|
85
76
|
|
|
86
77
|
# Check if an object quacks like a serde (has serialize + deserialize).
|
|
87
|
-
sig { params(obj: T.untyped).returns(T::Boolean) }
|
|
88
78
|
def serde?(obj)
|
|
89
79
|
obj.respond_to?(:serialize) && obj.respond_to?(:deserialize)
|
|
90
80
|
end
|
|
91
81
|
|
|
92
82
|
# Resolve a type or serde into a serde object with serialize/deserialize/json_schema.
|
|
93
|
-
sig { params(type_or_serde: T.untyped).returns(T.untyped) }
|
|
94
83
|
def resolve(type_or_serde)
|
|
95
84
|
return JsonSerde if type_or_serde.nil?
|
|
96
85
|
return type_or_serde if serde?(type_or_serde)
|
|
97
|
-
return TStructSerde.new(type_or_serde) if t_struct?(type_or_serde)
|
|
98
86
|
return DryStructSerde.new(type_or_serde) if dry_struct?(type_or_serde)
|
|
99
87
|
return TypeSerde.new(type_or_serde, PRIMITIVE_SCHEMAS[type_or_serde]) if PRIMITIVE_SCHEMAS.key?(type_or_serde)
|
|
100
88
|
return TypeSerde.new(type_or_serde, type_or_serde.json_schema) if type_or_serde.respond_to?(:json_schema)
|
|
@@ -102,60 +90,12 @@ module Restate
|
|
|
102
90
|
JsonSerde
|
|
103
91
|
end
|
|
104
92
|
|
|
105
|
-
# Check if a value is a T::Struct subclass.
|
|
106
|
-
sig { params(val: T.untyped).returns(T::Boolean) }
|
|
107
|
-
def t_struct?(val)
|
|
108
|
-
!!(val.is_a?(Class) && val < T::Struct)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
93
|
# Check if a value is a Dry::Struct subclass.
|
|
112
|
-
sig { params(val: T.untyped).returns(T.nilable(T::Boolean)) }
|
|
113
94
|
def dry_struct?(val)
|
|
114
95
|
defined?(::Dry::Struct) && val.is_a?(Class) && val < ::Dry::Struct
|
|
115
96
|
end
|
|
116
97
|
|
|
117
|
-
# Generate a JSON Schema from a T::Struct class by introspecting its props.
|
|
118
|
-
sig { params(struct_class: T.class_of(T::Struct)).returns(T::Hash[String, T.untyped]) }
|
|
119
|
-
def t_struct_to_json_schema(struct_class) # rubocop:disable Metrics
|
|
120
|
-
properties = {}
|
|
121
|
-
required = []
|
|
122
|
-
|
|
123
|
-
T.unsafe(struct_class).props.each do |name, meta|
|
|
124
|
-
prop_name = (meta[:serialized_form] || name).to_s
|
|
125
|
-
properties[prop_name] = t_type_to_json_schema(meta[:type_object] || meta[:type])
|
|
126
|
-
required << prop_name unless meta[:fully_optional] || meta[:_tnilable]
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
schema = { 'type' => 'object', 'properties' => properties }
|
|
130
|
-
schema['required'] = required unless required.empty?
|
|
131
|
-
schema
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Convert a Sorbet T::Types type object to a JSON Schema hash.
|
|
135
|
-
sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
|
|
136
|
-
def t_type_to_json_schema(type) # rubocop:disable Metrics
|
|
137
|
-
case type
|
|
138
|
-
when T::Types::Simple
|
|
139
|
-
PRIMITIVE_SCHEMAS[type.raw_type] || {}
|
|
140
|
-
when T::Types::Union
|
|
141
|
-
schemas = type.types.map { |t| t_type_to_json_schema(t) }
|
|
142
|
-
schemas.uniq!
|
|
143
|
-
schemas.length == 1 ? schemas.first : { 'anyOf' => schemas }
|
|
144
|
-
when T::Types::TypedArray
|
|
145
|
-
{ 'type' => 'array', 'items' => t_type_to_json_schema(type.type) }
|
|
146
|
-
when T::Types::TypedHash
|
|
147
|
-
{ 'type' => 'object' }
|
|
148
|
-
when Class
|
|
149
|
-
return t_struct_to_json_schema(type) if type < T::Struct
|
|
150
|
-
|
|
151
|
-
PRIMITIVE_SCHEMAS[type] || {}
|
|
152
|
-
else
|
|
153
|
-
{}
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
98
|
# Generate a JSON Schema from a Dry::Struct class.
|
|
158
|
-
sig { params(struct_class: T.untyped).returns(T::Hash[String, T.untyped]) }
|
|
159
99
|
def dry_struct_to_json_schema(struct_class)
|
|
160
100
|
properties = {}
|
|
161
101
|
required = []
|
|
@@ -172,7 +112,6 @@ module Restate
|
|
|
172
112
|
end
|
|
173
113
|
|
|
174
114
|
# Convert a dry-types type to a JSON Schema hash.
|
|
175
|
-
sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
|
|
176
115
|
def dry_type_to_json_schema(type) # rubocop:disable Metrics
|
|
177
116
|
type_class = type.class.name || ''
|
|
178
117
|
|
|
@@ -198,7 +137,6 @@ module Restate
|
|
|
198
137
|
end
|
|
199
138
|
|
|
200
139
|
# Convert a nominal dry-type (with .primitive) to JSON Schema.
|
|
201
|
-
sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
|
|
202
140
|
def nominal_to_json_schema(type)
|
|
203
141
|
prim = type.primitive
|
|
204
142
|
return dry_struct_to_json_schema(prim) if dry_struct?(prim)
|
|
@@ -210,32 +148,25 @@ module Restate
|
|
|
210
148
|
# Serde wrapper for primitive types and classes with a .json_schema method.
|
|
211
149
|
# Delegates serialize/deserialize to JsonSerde, adds schema.
|
|
212
150
|
class TypeSerde
|
|
213
|
-
extend T::Sig
|
|
214
|
-
|
|
215
|
-
sig { returns(T.untyped) }
|
|
216
151
|
attr_reader :type_class
|
|
217
152
|
|
|
218
153
|
# Create a TypeSerde for the given type with a precomputed JSON Schema.
|
|
219
|
-
sig { params(type: T.untyped, schema: T.nilable(T::Hash[String, T.untyped])).void }
|
|
220
154
|
def initialize(type, schema)
|
|
221
155
|
@type_class = type
|
|
222
156
|
@schema = schema
|
|
223
157
|
end
|
|
224
158
|
|
|
225
159
|
# Serialize a Ruby object to JSON bytes via JsonSerde.
|
|
226
|
-
sig { params(obj: T.untyped).returns(String) }
|
|
227
160
|
def serialize(obj)
|
|
228
161
|
JsonSerde.serialize(obj)
|
|
229
162
|
end
|
|
230
163
|
|
|
231
164
|
# Deserialize JSON bytes to a Ruby object via JsonSerde.
|
|
232
|
-
sig { params(buf: T.nilable(String)).returns(T.untyped) }
|
|
233
165
|
def deserialize(buf)
|
|
234
166
|
JsonSerde.deserialize(buf)
|
|
235
167
|
end
|
|
236
168
|
|
|
237
169
|
# Return the JSON Schema for this type.
|
|
238
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
239
170
|
def json_schema
|
|
240
171
|
@schema
|
|
241
172
|
end
|
|
@@ -244,28 +175,22 @@ module Restate
|
|
|
244
175
|
# Serde for Dry::Struct types.
|
|
245
176
|
# Deserializes JSON into struct instances, serializes structs to JSON.
|
|
246
177
|
class DryStructSerde
|
|
247
|
-
extend T::Sig
|
|
248
|
-
|
|
249
|
-
sig { returns(T.untyped) }
|
|
250
178
|
attr_reader :struct_class
|
|
251
179
|
|
|
252
180
|
# Create a DryStructSerde for the given Dry::Struct class.
|
|
253
|
-
sig { params(struct_class: T.untyped).void }
|
|
254
181
|
def initialize(struct_class)
|
|
255
182
|
@struct_class = struct_class
|
|
256
183
|
end
|
|
257
184
|
|
|
258
185
|
# Serialize a Dry::Struct (or hash-like object) to JSON bytes.
|
|
259
|
-
sig { params(obj: T.untyped).returns(String) }
|
|
260
186
|
def serialize(obj)
|
|
261
|
-
return
|
|
187
|
+
return EMPTY_BYTES if obj.nil?
|
|
262
188
|
|
|
263
189
|
hash = obj.respond_to?(:to_h) ? obj.to_h : obj
|
|
264
190
|
JSON.generate(hash).b
|
|
265
191
|
end
|
|
266
192
|
|
|
267
193
|
# Deserialize JSON bytes into a Dry::Struct instance.
|
|
268
|
-
sig { params(buf: T.nilable(String)).returns(T.untyped) }
|
|
269
194
|
def deserialize(buf)
|
|
270
195
|
return nil if buf.nil? || buf.empty?
|
|
271
196
|
|
|
@@ -274,49 +199,8 @@ module Restate
|
|
|
274
199
|
end
|
|
275
200
|
|
|
276
201
|
# Return the JSON Schema derived from the Dry::Struct definition.
|
|
277
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
|
278
202
|
def json_schema
|
|
279
203
|
@json_schema ||= Serde.dry_struct_to_json_schema(@struct_class)
|
|
280
204
|
end
|
|
281
205
|
end
|
|
282
|
-
|
|
283
|
-
# Serde for T::Struct types (Sorbet's native typed structs).
|
|
284
|
-
# Uses T::Struct#serialize for output and T::Struct.from_hash for input.
|
|
285
|
-
# Generates JSON Schema from T::Struct props introspection.
|
|
286
|
-
class TStructSerde
|
|
287
|
-
extend T::Sig
|
|
288
|
-
|
|
289
|
-
sig { returns(T.class_of(T::Struct)) }
|
|
290
|
-
attr_reader :struct_class
|
|
291
|
-
|
|
292
|
-
# Create a TStructSerde for the given T::Struct subclass.
|
|
293
|
-
sig { params(struct_class: T.class_of(T::Struct)).void }
|
|
294
|
-
def initialize(struct_class)
|
|
295
|
-
@struct_class = struct_class
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
# Serialize a T::Struct instance to JSON bytes.
|
|
299
|
-
sig { params(obj: T.untyped).returns(String) }
|
|
300
|
-
def serialize(obj)
|
|
301
|
-
return ''.b if obj.nil?
|
|
302
|
-
|
|
303
|
-
hash = obj.is_a?(T::Struct) ? obj.serialize : obj
|
|
304
|
-
JSON.generate(hash).b
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# Deserialize JSON bytes into a T::Struct instance.
|
|
308
|
-
sig { params(buf: T.nilable(String)).returns(T.untyped) }
|
|
309
|
-
def deserialize(buf)
|
|
310
|
-
return nil if buf.nil? || buf.empty?
|
|
311
|
-
|
|
312
|
-
hash = JSON.parse(buf)
|
|
313
|
-
T.unsafe(@struct_class).from_hash(hash)
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# Return the JSON Schema derived from the T::Struct props.
|
|
317
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
|
318
|
-
def json_schema
|
|
319
|
-
@json_schema ||= Serde.t_struct_to_json_schema(@struct_class)
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
206
|
end
|
data/lib/restate/server.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: ignore
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'async'
|
|
@@ -14,21 +14,17 @@ module Restate
|
|
|
14
14
|
# GET /health → health check
|
|
15
15
|
# POST /invoke/:service/:handler → handler invocation
|
|
16
16
|
class Server
|
|
17
|
-
|
|
17
|
+
SDK_VERSION = Internal::SDK_VERSION
|
|
18
|
+
X_RESTATE_SERVER = "restate-sdk-ruby/#{SDK_VERSION}".freeze
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
X_RESTATE_SERVER = T.let("restate-sdk-ruby/#{SDK_VERSION}".freeze, String)
|
|
20
|
+
LOGGER = Logger.new($stdout, progname: 'Restate::Server')
|
|
21
21
|
|
|
22
|
-
LOGGER = T.let(Logger.new($stdout, progname: 'Restate::Server'), Logger)
|
|
23
|
-
|
|
24
|
-
sig { params(endpoint: Endpoint).void }
|
|
25
22
|
def initialize(endpoint)
|
|
26
|
-
@endpoint =
|
|
27
|
-
@identity_verifier =
|
|
23
|
+
@endpoint = endpoint
|
|
24
|
+
@identity_verifier = Internal::IdentityVerifier.new(endpoint.identity_keys)
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
# Rack interface
|
|
31
|
-
sig { params(env: T::Hash[String, T.untyped]).returns(T.untyped) }
|
|
32
28
|
def call(env)
|
|
33
29
|
path = env['PATH_INFO'] || '/'
|
|
34
30
|
parsed = parse_path(path)
|
|
@@ -51,7 +47,6 @@ module Restate
|
|
|
51
47
|
|
|
52
48
|
private
|
|
53
49
|
|
|
54
|
-
sig { params(path: String).returns(T::Hash[Symbol, T.untyped]) }
|
|
55
50
|
def parse_path(path)
|
|
56
51
|
segments = path.split('/').reject(&:empty?)
|
|
57
52
|
|
|
@@ -77,22 +72,18 @@ module Restate
|
|
|
77
72
|
end
|
|
78
73
|
end
|
|
79
74
|
|
|
80
|
-
sig { returns(T.untyped) }
|
|
81
75
|
def health_response
|
|
82
76
|
[200, { 'content-type' => 'application/json', 'x-restate-server' => X_RESTATE_SERVER }, ['{"status":"ok"}']]
|
|
83
77
|
end
|
|
84
78
|
|
|
85
|
-
sig { returns(T.untyped) }
|
|
86
79
|
def not_found_response
|
|
87
80
|
[404, { 'x-restate-server' => X_RESTATE_SERVER }, ['']]
|
|
88
81
|
end
|
|
89
82
|
|
|
90
|
-
sig { params(status: Integer, message: String).returns(T.untyped) }
|
|
91
83
|
def error_response(status, message)
|
|
92
84
|
[status, { 'content-type' => 'text/plain', 'x-restate-server' => X_RESTATE_SERVER }, [message]]
|
|
93
85
|
end
|
|
94
86
|
|
|
95
|
-
sig { params(env: T::Hash[String, T.untyped]).returns(T.untyped) }
|
|
96
87
|
def handle_discover(env)
|
|
97
88
|
# Detect HTTP version for protocol mode
|
|
98
89
|
http_version = env['HTTP_VERSION'] || env['SERVER_PROTOCOL'] || 'HTTP/1.1'
|
|
@@ -119,7 +110,6 @@ module Restate
|
|
|
119
110
|
end
|
|
120
111
|
end
|
|
121
112
|
|
|
122
|
-
sig { params(accept: String).returns(T.nilable(Integer)) }
|
|
123
113
|
def negotiate_version(accept)
|
|
124
114
|
if accept.include?('application/vnd.restate.endpointmanifest.v4+json')
|
|
125
115
|
4
|
|
@@ -132,7 +122,6 @@ module Restate
|
|
|
132
122
|
end
|
|
133
123
|
end
|
|
134
124
|
|
|
135
|
-
sig { params(env: T::Hash[String, T.untyped], service_name: T.untyped, handler_name: T.untyped).returns(T.untyped) }
|
|
136
125
|
def handle_invocation(env, service_name, handler_name)
|
|
137
126
|
# Verify identity
|
|
138
127
|
request_headers = extract_headers(env)
|
|
@@ -154,7 +143,6 @@ module Restate
|
|
|
154
143
|
process_invocation(env, handler, request_headers)
|
|
155
144
|
end
|
|
156
145
|
|
|
157
|
-
sig { params(env: T::Hash[String, T.untyped], handler: T.untyped, request_headers: T.untyped).returns(T.untyped) }
|
|
158
146
|
def process_invocation(env, handler, request_headers)
|
|
159
147
|
vm = VMWrapper.new(request_headers)
|
|
160
148
|
status, response_headers = vm.get_response_head
|
|
@@ -171,7 +159,7 @@ module Restate
|
|
|
171
159
|
# Read request body chunks and feed to VM until ready to execute,
|
|
172
160
|
# then continue feeding remaining chunks via the input queue.
|
|
173
161
|
rack_input = env['rack.input']
|
|
174
|
-
ready =
|
|
162
|
+
ready = false
|
|
175
163
|
if rack_input
|
|
176
164
|
# Feed chunks until the VM has enough to start execution
|
|
177
165
|
while (chunk = rack_input.read_partial(16_384))
|
|
@@ -205,7 +193,8 @@ module Restate
|
|
|
205
193
|
invocation: invocation,
|
|
206
194
|
send_output: send_output,
|
|
207
195
|
input_queue: input_queue,
|
|
208
|
-
middleware: @endpoint.middleware
|
|
196
|
+
middleware: @endpoint.middleware,
|
|
197
|
+
outbound_middleware: @endpoint.outbound_middleware
|
|
209
198
|
)
|
|
210
199
|
|
|
211
200
|
# Spawn the handler as an async task so the response body can stream
|
|
@@ -246,11 +235,8 @@ module Restate
|
|
|
246
235
|
# Rack 3 streaming body that yields chunks from an Async::Queue.
|
|
247
236
|
# Terminates when nil is dequeued.
|
|
248
237
|
class StreamingBody
|
|
249
|
-
extend T::Sig
|
|
250
|
-
|
|
251
|
-
sig { params(queue: Async::Queue).void }
|
|
252
238
|
def initialize(queue)
|
|
253
|
-
@queue =
|
|
239
|
+
@queue = queue
|
|
254
240
|
end
|
|
255
241
|
|
|
256
242
|
def each
|
|
@@ -263,13 +249,12 @@ module Restate
|
|
|
263
249
|
end
|
|
264
250
|
end
|
|
265
251
|
|
|
266
|
-
sig { params(env: T::Hash[String, T.untyped]).returns(T::Array[T::Array[String]]) }
|
|
267
252
|
def extract_headers(env)
|
|
268
|
-
headers =
|
|
253
|
+
headers = []
|
|
269
254
|
env.each do |key, value|
|
|
270
255
|
next unless key.start_with?('HTTP_')
|
|
271
256
|
|
|
272
|
-
header_name = key.
|
|
257
|
+
header_name = key.byteslice(5..).tr('_', '-').downcase!
|
|
273
258
|
headers << [header_name, value]
|
|
274
259
|
end
|
|
275
260
|
# Also include content-type and content-length if present
|