fiber_stream 0.3.0 → 0.5.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.
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberStream
4
+ # Producer-side context for `Source.ractor_producer`.
5
+ #
6
+ # Producer blocks call `emit`, `complete`, or `fail` to send one protocol
7
+ # message after receiving one downstream acknowledgment. A `false` return
8
+ # means cooperative cancellation was observed before the requested message
9
+ # could be sent.
10
+ class RactorProducer
11
+ def initialize(data_port, ack_port, transfer)
12
+ @data_port = data_port
13
+ @ack_port = ack_port
14
+ @transfer = transfer
15
+ @terminal = false
16
+ @cancelled = false
17
+ @send_failed = false
18
+ end
19
+
20
+ def emit(value, transfer: nil)
21
+ return false if terminal? || cancelled?
22
+
23
+ message_transfer = validate_transfer_override!(transfer)
24
+ return false unless wait_for_ack
25
+
26
+ send_emitted_message(RactorPort::Element.new(value), message_transfer)
27
+ end
28
+
29
+ def complete
30
+ return false if terminal? || cancelled?
31
+ return false unless wait_for_ack
32
+
33
+ send_terminal_message(RactorPort::Complete.new)
34
+ end
35
+
36
+ def fail(error = nil, cause_class_name: nil, cause_message: nil)
37
+ return false if terminal? || cancelled?
38
+
39
+ failure = failure_message(error, cause_class_name, cause_message)
40
+ return false unless wait_for_ack
41
+
42
+ send_terminal_message(failure)
43
+ end
44
+
45
+ def cancelled?
46
+ @cancelled
47
+ end
48
+
49
+ def terminal? # :nodoc:
50
+ @terminal
51
+ end
52
+
53
+ def send_failed? # :nodoc:
54
+ @send_failed
55
+ end
56
+
57
+ private
58
+
59
+ def send_emitted_message(message, transfer)
60
+ send_data_message(message, transfer)
61
+ true
62
+ rescue Exception => error # rubocop:disable Lint/RescueException
63
+ report_same_ack_failure(error)
64
+ false
65
+ end
66
+
67
+ def send_terminal_message(message)
68
+ send_data_message(message, @transfer)
69
+ @terminal = true
70
+ true
71
+ rescue Exception => send_error # rubocop:disable Lint/RescueException
72
+ report_same_ack_failure(send_error)
73
+ false
74
+ end
75
+
76
+ def validate_transfer_override!(transfer)
77
+ return @transfer if transfer.nil?
78
+ return transfer if [:copy, :move].include?(transfer)
79
+
80
+ raise ArgumentError, "transfer must be :copy or :move"
81
+ end
82
+
83
+ def wait_for_ack
84
+ case @ack_port.receive
85
+ in RactorPort::Ack
86
+ true
87
+ in RactorPort::Cancel
88
+ @cancelled = true
89
+ false
90
+ else
91
+ raise TypeError, "invalid ractor producer control message"
92
+ end
93
+ end
94
+
95
+ def send_data_message(message, transfer)
96
+ if transfer == :move
97
+ @data_port.send(message, move: true)
98
+ else
99
+ @data_port.send(message)
100
+ end
101
+ end
102
+
103
+ def failure_message(error, cause_class_name, cause_message)
104
+ if error
105
+ return RactorPort::Failure.new(safe_class_name(error), safe_message(error))
106
+ end
107
+
108
+ unless cause_class_name.is_a?(String) && cause_message.is_a?(String)
109
+ raise ArgumentError, "fail requires an error or String failure metadata"
110
+ end
111
+
112
+ RactorPort::Failure.new(cause_class_name, cause_message)
113
+ end
114
+
115
+ def safe_class_name(error)
116
+ name = error.class.name
117
+ name.is_a?(String) && !name.empty? ? name : "Exception"
118
+ rescue Exception # rubocop:disable Lint/RescueException
119
+ "Exception"
120
+ end
121
+
122
+ def safe_message(error)
123
+ message = error.message
124
+ message.is_a?(String) ? message : ""
125
+ rescue Exception # rubocop:disable Lint/RescueException
126
+ ""
127
+ end
128
+
129
+ def report_same_ack_failure(error)
130
+ send_data_message(RactorPort::Failure.new(safe_class_name(error), safe_message(error)), :copy)
131
+ @terminal = true
132
+ rescue Exception # rubocop:disable Lint/RescueException
133
+ @terminal = true
134
+ @send_failed = true
135
+ end
136
+ end
137
+
138
+ # Builder passed to `Source.ractor_merge_producers`.
139
+ #
140
+ # Each `producer` call records one lazily started owned producer definition.
141
+ # Registration validates producer block isolation and transfer policy but
142
+ # does not create Ractor ports or start producer code.
143
+ class RactorProducerGroup
144
+ Definition = Data.define(:args, :transfer, :block)
145
+ private_constant :Definition
146
+
147
+ def initialize(default_transfer)
148
+ @default_transfer = default_transfer
149
+ @definitions = []
150
+ end
151
+
152
+ def producer(*args, transfer: nil, &block)
153
+ raise ArgumentError, "missing block" unless block
154
+ unless transfer.nil? || [:copy, :move].include?(transfer)
155
+ raise ArgumentError, "transfer must be :copy or :move"
156
+ end
157
+ raise TypeError, "block must be shareable" unless Ractor.shareable?(block)
158
+
159
+ @definitions << Definition.new(args:, transfer: transfer || @default_transfer, block:)
160
+ self
161
+ end
162
+
163
+ def definitions
164
+ @definitions.dup.freeze
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberStream
4
+ # Scheduler-aware token-bucket rate limiter.
5
+ #
6
+ # `rate` permits refill every `per` seconds. `burst` is the maximum token
7
+ # capacity and defaults to `rate`. Immediate permit grants do not require a
8
+ # scheduler. When FiberStream must sleep, the current fiber must be
9
+ # non-blocking with an installed `Fiber.scheduler`.
10
+ class RateLimiter
11
+ Request = Data.define(:rate, :per, :burst, :permits, :now)
12
+
13
+ class << self
14
+ def validate_options!(rate:, per: 1, burst: nil) # :nodoc:
15
+ rate = validate_rate(rate)
16
+ validate_duration(:per, per)
17
+ validate_burst(burst.nil? ? rate : burst)
18
+ nil
19
+ end
20
+
21
+ private
22
+
23
+ def validate_rate(rate)
24
+ raise TypeError, "rate must be an Integer" unless rate.is_a?(Integer)
25
+ raise ArgumentError, "rate must be positive" unless rate.positive?
26
+
27
+ rate
28
+ end
29
+
30
+ def validate_burst(burst)
31
+ raise TypeError, "burst must be an Integer" unless burst.is_a?(Integer)
32
+ raise ArgumentError, "burst must be positive" unless burst.positive?
33
+
34
+ burst
35
+ end
36
+
37
+ def validate_duration(name, duration)
38
+ raise TypeError, "#{name} must be Numeric" unless duration.is_a?(Numeric)
39
+ raise ArgumentError, "#{name} must be finite and real" unless finite_real?(duration)
40
+ raise ArgumentError, "#{name} must be positive" unless duration.positive?
41
+
42
+ duration
43
+ end
44
+
45
+ def finite_real?(value)
46
+ return false if value.is_a?(Complex)
47
+ return value.finite? if value.respond_to?(:finite?)
48
+
49
+ true
50
+ end
51
+ end
52
+
53
+ def initialize(rate:, per: 1, burst: nil, &block)
54
+ self.class.validate_options!(rate:, per:, burst:)
55
+
56
+ @rate = rate
57
+ @per = per
58
+ @burst = burst.nil? ? @rate : burst
59
+ @policy = block
60
+ @mutex = Mutex.new
61
+ @tokens = @burst.to_f
62
+ @updated_at = monotonic_now
63
+ end
64
+
65
+ # Acquires `permits`, waiting when necessary.
66
+ #
67
+ # Waits are scheduler-backed and non-blocking. Requests larger than `burst`
68
+ # are rejected because the local token bucket could never satisfy them.
69
+ def acquire(permits: 1)
70
+ permits = validate_permits(permits)
71
+
72
+ if @policy
73
+ acquire_with_policy(permits)
74
+ else
75
+ acquire_with_token_bucket(permits)
76
+ end
77
+
78
+ nil
79
+ end
80
+
81
+ private
82
+
83
+ def acquire_with_policy(permits)
84
+ loop do
85
+ wait = normalize_policy_wait(@policy.call(request_for(permits)))
86
+ return if wait <= 0
87
+
88
+ scheduler_sleep(wait)
89
+ end
90
+ end
91
+
92
+ def acquire_with_token_bucket(permits)
93
+ loop do
94
+ wait = nil
95
+
96
+ @mutex.synchronize do
97
+ refill_tokens
98
+
99
+ if @tokens >= permits
100
+ @tokens -= permits
101
+ return
102
+ end
103
+
104
+ wait = (permits - @tokens) / permits_per_second
105
+ end
106
+
107
+ scheduler_sleep(wait)
108
+ end
109
+ end
110
+
111
+ def request_for(permits)
112
+ Request.new(rate: @rate, per: @per, burst: @burst, permits:, now: monotonic_now)
113
+ end
114
+
115
+ def refill_tokens
116
+ now = monotonic_now
117
+ elapsed = now - @updated_at
118
+ @updated_at = now
119
+ @tokens = [@burst, @tokens + (elapsed * permits_per_second)].min
120
+ end
121
+
122
+ def permits_per_second
123
+ @rate.to_f / @per.to_f
124
+ end
125
+
126
+ def scheduler_sleep(duration)
127
+ validate_scheduler!
128
+ sleep(duration)
129
+ end
130
+
131
+ def validate_scheduler!
132
+ return if Fiber.scheduler && !Fiber.current.blocking?
133
+
134
+ message =
135
+ if Fiber.scheduler
136
+ "RateLimiter#acquire requires a non-blocking fiber"
137
+ else
138
+ "RateLimiter#acquire requires Fiber.scheduler"
139
+ end
140
+ raise SchedulerRequiredError, message
141
+ end
142
+
143
+ def validate_permits(permits)
144
+ raise TypeError, "permits must be an Integer" unless permits.is_a?(Integer)
145
+ raise ArgumentError, "permits must be positive" unless permits.positive?
146
+ raise ArgumentError, "permits must be less than or equal to burst" if permits > @burst
147
+
148
+ permits
149
+ end
150
+
151
+ def normalize_policy_wait(wait)
152
+ return 0 if wait.nil?
153
+ raise TypeError, "custom wait duration must be Numeric or nil" unless wait.is_a?(Numeric)
154
+ raise ArgumentError, "custom wait duration must be finite and real" unless self.class.send(:finite_real?, wait)
155
+
156
+ wait.positive? ? wait : 0
157
+ end
158
+
159
+ def monotonic_now
160
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
161
+ end
162
+ end
163
+ end
@@ -7,6 +7,10 @@ module FiberStream
7
7
  CancelledMessage = Data.define(:error)
8
8
  private_constant :ValueMessage, :ErrorMessage, :CancelledMessage
9
9
 
10
+ def self.start(scheduler, &run) # :nodoc:
11
+ new(scheduler, &run)
12
+ end
13
+
10
14
  def initialize(scheduler, &run)
11
15
  @scheduler = scheduler
12
16
  @completion = nil
@@ -10,10 +10,7 @@ module FiberStream
10
10
  new do |stream|
11
11
  values = []
12
12
 
13
- loop do
14
- value = stream.next
15
- break if Pull.done?(value)
16
-
13
+ Pull.each_value(stream) do |value|
17
14
  values << value
18
15
  end
19
16
 
@@ -32,6 +29,22 @@ module FiberStream
32
29
  end
33
30
  end
34
31
 
32
+ # Creates a sink that counts all stream elements.
33
+ #
34
+ # The sink consumes upstream until normal completion and returns the number
35
+ # of elements observed. It does not store consumed elements.
36
+ def self.count
37
+ new do |stream|
38
+ count = 0
39
+
40
+ Pull.each_value(stream) do
41
+ count += 1
42
+ end
43
+
44
+ count
45
+ end
46
+ end
47
+
35
48
  # Creates a sink that folds all stream elements into an accumulator.
36
49
  #
37
50
  # The sink consumes upstream until normal completion. It returns the final
@@ -45,10 +58,7 @@ module FiberStream
45
58
  new do |stream|
46
59
  accumulator = initial
47
60
 
48
- loop do
49
- value = stream.next
50
- break if Pull.done?(value)
51
-
61
+ Pull.each_value(stream) do |value|
52
62
  accumulator = block.call(accumulator, value)
53
63
  end
54
64
 
@@ -68,10 +78,7 @@ module FiberStream
68
78
  new do |stream|
69
79
  count = 0
70
80
 
71
- loop do
72
- value = stream.next
73
- break if Pull.done?(value)
74
-
81
+ Pull.each_value(stream) do |value|
75
82
  block.call(value)
76
83
  count += 1
77
84
  end
@@ -99,15 +106,17 @@ module FiberStream
99
106
  end
100
107
  end
101
108
 
109
+ def self.build(&run) # :nodoc:
110
+ new(&run)
111
+ end
112
+
102
113
  def initialize(&run)
103
114
  @run = run
104
115
  end
105
116
 
106
117
  private_class_method :new
107
118
 
108
- private
109
-
110
- def run(stream)
119
+ def run_stream(stream) # :nodoc:
111
120
  @run.call(stream)
112
121
  end
113
122
 
@@ -121,10 +130,7 @@ module FiberStream
121
130
  end
122
131
 
123
132
  def run(stream)
124
- loop do
125
- value = stream.next
126
- break if Pull.done?(value)
127
-
133
+ Pull.each_value(stream) do |value|
128
134
  write(value)
129
135
  end
130
136
 
@@ -47,7 +47,7 @@ module FiberStream
47
47
  raise TypeError, "ack_port must provide Ractor-style send"
48
48
  end
49
49
 
50
- Flow.__send__(:validate_ractor_transfer_policy!, :ack_transfer, ack_transfer)
50
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
51
51
  raise TypeError, "cancel must be true or false" unless [true, false].include?(cancel)
52
52
 
53
53
  new(-> { Pull.ractor_port(port, ack_port, ack_transfer, cancel) })
@@ -65,12 +65,56 @@ module FiberStream
65
65
  def self.ractor_merge_ports(ports, ack_transfer: :copy, cancel: true)
66
66
  pairs = normalize_ractor_merge_port_pairs(ports)
67
67
 
68
- Flow.__send__(:validate_ractor_transfer_policy!, :ack_transfer, ack_transfer)
68
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
69
69
  raise TypeError, "cancel must be true or false" unless [true, false].include?(cancel)
70
70
 
71
71
  new(-> { Pull.ractor_merge_ports(pairs, ack_transfer, cancel) })
72
72
  end
73
73
 
74
+ # Creates a source backed by one FiberStream-owned producer ractor.
75
+ #
76
+ # The producer ractor is started lazily on first downstream demand. The
77
+ # shareable block receives a `RactorProducer` context and the provided
78
+ # arguments. Calls to the context preserve one-outstanding-ack
79
+ # backpressure, and cleanup always requests cooperative cancellation.
80
+ def self.ractor_producer(*args, transfer: :copy, ack_transfer: :copy, &block)
81
+ raise ArgumentError, "missing block" unless block
82
+
83
+ Internal::RactorTransferPolicy.validate!(:transfer, transfer)
84
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
85
+ raise TypeError, "block must be shareable" unless Ractor.shareable?(block)
86
+
87
+ group = RactorProducerGroup.new(transfer)
88
+ group.producer(*args, &block)
89
+ definitions = group.definitions
90
+
91
+ new(-> { Pull.ractor_producer(definitions, ack_transfer) })
92
+ end
93
+
94
+ # Creates a source backed by multiple FiberStream-owned producer ractors.
95
+ #
96
+ # The registration block runs at construction to collect producer
97
+ # definitions, but producer ractors and ports are started lazily on first
98
+ # downstream demand. Outputs are merged with the same ready-order semantics
99
+ # as `Source.ractor_merge_ports`.
100
+ def self.ractor_merge_producers(transfer: :copy, ack_transfer: :copy, &block)
101
+ raise ArgumentError, "missing block" unless block
102
+
103
+ Internal::RactorTransferPolicy.validate!(:transfer, transfer)
104
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
105
+
106
+ group = RactorProducerGroup.new(transfer)
107
+ block.call(group)
108
+ definitions = group.definitions
109
+ raise ArgumentError, "ractor_merge_producers requires at least two producers" if definitions.size < 2
110
+
111
+ new(-> { Pull.ractor_merge_producers(definitions, ack_transfer) })
112
+ end
113
+
114
+ def self.build(source_factory, flows = []) # :nodoc:
115
+ new(source_factory, flows)
116
+ end
117
+
74
118
  def initialize(source_factory, flows = [])
75
119
  @source_factory = source_factory
76
120
  @flows = flows
@@ -83,7 +127,7 @@ module FiberStream
83
127
  def via(flow)
84
128
  raise TypeError, "expected FiberStream::Flow" unless flow.is_a?(Flow)
85
129
 
86
- self.class.__send__(:new, @source_factory, @flows + [flow])
130
+ self.class.build(@source_factory, @flows + [flow])
87
131
  end
88
132
 
89
133
  # Returns a new source definition that emits this source, then `source`.
@@ -95,10 +139,7 @@ module FiberStream
95
139
  def concat(source)
96
140
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
97
141
 
98
- self.class.__send__(
99
- :new,
100
- -> { Pull.concat(materializer, source.__send__(:materializer)) }
101
- )
142
+ self.class.build(-> { Pull.concat(to_pull_materializer, source.to_pull_materializer) })
102
143
  end
103
144
 
104
145
  # Returns a new source definition that emits pairs from this source and
@@ -111,10 +152,7 @@ module FiberStream
111
152
  def zip(source)
112
153
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
113
154
 
114
- self.class.__send__(
115
- :new,
116
- -> { Pull.zip(materializer, source.__send__(:materializer)) }
117
- )
155
+ self.class.build(-> { Pull.zip(to_pull_materializer, source.to_pull_materializer) })
118
156
  end
119
157
 
120
158
  # Returns a new source definition that emits values from this source and
@@ -128,10 +166,7 @@ module FiberStream
128
166
  def merge(source)
129
167
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
130
168
 
131
- self.class.__send__(
132
- :new,
133
- -> { Pull.merge(materializer, source.__send__(:materializer)) }
134
- )
169
+ self.class.build(-> { Pull.merge(to_pull_materializer, source.to_pull_materializer) })
135
170
  end
136
171
 
137
172
  # Returns a new source definition that maps each element with `block`.
@@ -143,6 +178,35 @@ module FiberStream
143
178
  via(Flow.map(&block))
144
179
  end
145
180
 
181
+ # Returns a new source definition that emits truthy transformed values.
182
+ #
183
+ # This is a convenience wrapper around
184
+ # `via(FiberStream::Flow.filter_map { ... })` and has the same falsey-drop,
185
+ # lazy construction, error, and backpressure behavior as the underlying
186
+ # flow.
187
+ def filter_map(&block)
188
+ via(Flow.filter_map(&block))
189
+ end
190
+
191
+ # Returns a new source definition that drops nil elements.
192
+ #
193
+ # This is a convenience wrapper around `via(FiberStream::Flow.compact)` and
194
+ # preserves the same nil-only filtering, lazy construction, and
195
+ # backpressure behavior as the underlying flow.
196
+ def compact
197
+ via(Flow.compact)
198
+ end
199
+
200
+ # Returns a new source definition that emits each mapped expansion.
201
+ #
202
+ # This is a convenience wrapper around
203
+ # `via(FiberStream::Flow.map_concat { ... })` and has the same one-level
204
+ # flattening, lazy construction, error, and backpressure behavior as the
205
+ # underlying flow.
206
+ def map_concat(&block)
207
+ via(Flow.map_concat(&block))
208
+ end
209
+
146
210
  # Returns a new source definition that maps elements concurrently.
147
211
  #
148
212
  # This is a convenience wrapper around
@@ -153,6 +217,18 @@ module FiberStream
153
217
  via(Flow.parallel_map(concurrency: concurrency, &block))
154
218
  end
155
219
 
220
+ # Returns a new source definition that maps elements concurrently and emits
221
+ # mapped values in completion order.
222
+ #
223
+ # This is a convenience wrapper around
224
+ # `via(FiberStream::Flow.parallel_unordered_map(concurrency:) { ... })`.
225
+ # The operation preserves the same scheduler requirement, validation,
226
+ # bounded upstream run-ahead, and cancellation behavior while making no
227
+ # input-order guarantee.
228
+ def parallel_unordered_map(concurrency:, &block)
229
+ via(Flow.parallel_unordered_map(concurrency: concurrency, &block))
230
+ end
231
+
156
232
  # Returns a new source definition that maps elements in Ractor workers.
157
233
  #
158
234
  # This is a convenience wrapper around
@@ -179,6 +255,15 @@ module FiberStream
179
255
  via(Flow.select(&block))
180
256
  end
181
257
 
258
+ # Returns a new source definition that drops elements matching `block`.
259
+ #
260
+ # This is a convenience wrapper around
261
+ # `via(FiberStream::Flow.reject { ... })` and has the same truthiness and
262
+ # lazy construction behavior as the underlying flow.
263
+ def reject(&block)
264
+ via(Flow.reject(&block))
265
+ end
266
+
182
267
  # Returns a new source definition that emits at most `count` elements.
183
268
  #
184
269
  # This is a convenience wrapper around `via(FiberStream::Flow.take(count))`
@@ -204,6 +289,15 @@ module FiberStream
204
289
  via(Flow.grouped(count))
205
290
  end
206
291
 
292
+ # Returns a new source definition that emits running accumulators.
293
+ #
294
+ # This is a convenience wrapper around
295
+ # `via(FiberStream::Flow.scan(initial) { ... })` and preserves the same
296
+ # reducer order, lazy construction, and pull-driven backpressure behavior.
297
+ def scan(initial, &block)
298
+ via(Flow.scan(initial, &block))
299
+ end
300
+
207
301
  # Returns a new source definition that emits leading elements while `block`
208
302
  # is truthy.
209
303
  #
@@ -241,6 +335,15 @@ module FiberStream
241
335
  via(Flow.buffer(count))
242
336
  end
243
337
 
338
+ # Returns a new source definition that rate-limits emitted elements.
339
+ #
340
+ # This is a convenience wrapper around `via(FiberStream::Flow.throttle(...))`.
341
+ # The `rate:` form creates a fresh default limiter for each materialization;
342
+ # pass `limiter:` to share quota state across sources or runs.
343
+ def throttle(**options)
344
+ via(Flow.throttle(**options))
345
+ end
346
+
244
347
  # Returns a new source definition that splits String chunks into lines.
245
348
  #
246
349
  # This is a convenience wrapper around
@@ -270,7 +373,7 @@ module FiberStream
270
373
  def to(sink)
271
374
  raise TypeError, "expected FiberStream::Sink" unless sink.is_a?(Sink)
272
375
 
273
- Pipeline.__send__(:new, self, sink)
376
+ Pipeline.build(self, sink)
274
377
  end
275
378
 
276
379
  # Materializes and runs this source with `sink`.
@@ -286,7 +389,7 @@ module FiberStream
286
389
  begin
287
390
  stream = materialize
288
391
 
289
- sink.__send__(:run, stream)
392
+ sink.run_stream(stream)
290
393
  rescue StandardError => error
291
394
  primary_error = error
292
395
  raise
@@ -301,6 +404,10 @@ module FiberStream
301
404
 
302
405
  private_class_method :new
303
406
 
407
+ def to_pull_materializer # :nodoc:
408
+ method(:materialize)
409
+ end
410
+
304
411
  def self.normalize_ractor_merge_port_pairs(ports)
305
412
  raise TypeError, "ports must respond to each" unless ports.respond_to?(:each)
306
413
 
@@ -341,17 +448,13 @@ module FiberStream
341
448
 
342
449
  private
343
450
 
344
- def materializer
345
- -> { materialize }
346
- end
347
-
348
451
  def materialize
349
452
  stream = nil
350
453
 
351
454
  begin
352
455
  stream = @source_factory.call
353
456
  @flows.each do |flow|
354
- stream = flow.__send__(:attach, stream)
457
+ stream = flow.attach_to(stream)
355
458
  end
356
459
  stream
357
460
  rescue StandardError
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FiberStream
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/fiber_stream.rb CHANGED
@@ -3,7 +3,10 @@
3
3
  require_relative "fiber_stream/pull"
4
4
  require_relative "fiber_stream/version"
5
5
  require_relative "fiber_stream/errors"
6
+ require_relative "fiber_stream/rate_limiter"
7
+ require_relative "fiber_stream/internal/ractor_transfer_policy"
6
8
  require_relative "fiber_stream/ractor_port"
9
+ require_relative "fiber_stream/ractor_producer"
7
10
  require_relative "fiber_stream/flow"
8
11
  require_relative "fiber_stream/sink"
9
12
  require_relative "fiber_stream/running_pipeline"