Class: FiberStream::Flow
- Inherits:
-
Object
- Object
- FiberStream::Flow
- Defined in:
- lib/fiber_stream/flow.rb,
sig/fiber_stream.rbs
Class Method Summary collapse
-
.async ⇒ void
Creates a scheduler-backed asynchronous boundary.
-
.buffer(count) ⇒ void
Creates a bounded asynchronous buffer.
-
.build(&attach) ⇒ Object
:nodoc:.
-
.compact ⇒ void
Creates a nil-dropping flow.
-
.drop(count) ⇒ void
Creates a fixed-prefix dropping flow.
-
.drop_while(&block) ⇒ void
Creates a predicate-based prefix-dropping flow.
-
.filter_map(&block) ⇒ void
Creates a transform-and-filter flow.
-
.grouped(count) ⇒ void
Creates a fixed-size grouping flow.
-
.lines(chomp: true, max_length: nil) ⇒ Flow[String, String]
Creates a line-splitting flow.
-
.map(&block) ⇒ void
Creates a mapping flow.
-
.map_concat(&block) ⇒ void
Creates a one-to-many mapping flow.
-
.parallel_map(concurrency:, &block) ⇒ void
Creates an ordered scheduler-backed parallel mapping flow.
-
.parallel_unordered_map(concurrency:, &block) ⇒ void
Creates an unordered scheduler-backed parallel mapping flow.
-
.ractor_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) ⇒ void
Creates an ordered Ractor-backed mapping flow.
-
.ractor_unordered_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) ⇒ void
Creates an unordered Ractor-backed mapping flow.
-
.reject(&block) ⇒ void
Creates a complement filtering flow.
-
.scan(initial, &block) ⇒ void
Creates a running-accumulator flow.
-
.select(&block) ⇒ void
Creates a filtering flow.
-
.split(separator, keep_separator: false, max_length: nil) ⇒ Flow[String, String]
Creates a delimiter-splitting flow.
-
.take(count) ⇒ void
Creates a limiting flow.
-
.take_while(&block) ⇒ void
Creates a predicate-based limiting flow.
-
.tap(&block) ⇒ void
Creates a pass-through observing flow.
-
.throttle(**options) ⇒ void
Creates a scheduler-aware throttling flow.
Instance Method Summary collapse
-
#attach_to(upstream) ⇒ Object
:nodoc:.
-
#initialize(&attach) ⇒ Flow
constructor
A new instance of Flow.
-
#to(sink) ⇒ void
Returns a sink that runs this flow before
sink. -
#via(flow) ⇒ void
Returns a reusable flow that applies this flow and then
flow.
Constructor Details
#initialize(&attach) ⇒ Flow
Returns a new instance of Flow.
399 400 401 |
# File 'lib/fiber_stream/flow.rb', line 399 def initialize(&attach) @attach = attach end |
Class Method Details
.async ⇒ void
This method returns an undefined value.
Creates a scheduler-backed asynchronous boundary.
The boundary starts its producer on the first downstream demand and
requires an installed Fiber.scheduler at that point. Upstream stages run
in a non-blocking producer fiber, downstream stages remain in the caller's
current fiber, and each downstream pull resumes at most one upstream pull.
Closing the boundary closes upstream and requests producer cancellation.
FiberStream does not depend on Async at runtime.
243 244 245 |
# File 'lib/fiber_stream/flow.rb', line 243 def self.async new { |upstream| Pull.async(upstream) } end |
.buffer(count) ⇒ void
This method returns an undefined value.
Creates a bounded asynchronous buffer.
The buffer starts its producer on the first downstream demand and requires
an installed Fiber.scheduler at that point. It preserves element order,
stores at most count messages, and closes upstream while requesting
producer cancellation when closed. count must be a positive Integer.
FiberStream does not depend on Async at runtime.
254 255 256 257 258 259 |
# File 'lib/fiber_stream/flow.rb', line 254 def self.buffer(count) raise TypeError, "count must be an Integer" unless count.is_a?(Integer) raise ArgumentError, "count must be positive" unless count.positive? new { |upstream| Pull.buffer(upstream, count) } end |
.build(&attach) ⇒ Object
:nodoc:
312 313 314 |
# File 'lib/fiber_stream/flow.rb', line 312 def self.build(&attach) # :nodoc: new(&attach) end |
.compact ⇒ void
This method returns an undefined value.
Creates a nil-dropping flow.
The flow drops nil elements and passes every non-nil element through
unchanged, including false.
32 33 34 |
# File 'lib/fiber_stream/flow.rb', line 32 def self.compact new { |upstream| Pull.compact(upstream) } end |
.drop(count) ⇒ void
This method returns an undefined value.
Creates a fixed-prefix dropping flow.
The flow discards the first count upstream elements, then passes later
elements through unchanged. drop(0) behaves as pass-through. Negative
counts raise ArgumentError; non-Integer counts raise TypeError.
178 179 180 181 182 183 |
# File 'lib/fiber_stream/flow.rb', line 178 def self.drop(count) raise TypeError, "count must be an Integer" unless count.is_a?(Integer) raise ArgumentError, "count must be non-negative" if count.negative? new { |upstream| Pull.drop(upstream, count) } end |
.drop_while(&block) ⇒ void
This method returns an undefined value.
Creates a predicate-based prefix-dropping flow.
The flow drops leading elements while the block result is truthy. The
first false or nil result, and all later elements, pass through unchanged.
After that boundary the block is not called again. Exceptions raised by
the block fail the stream and are re-raised from Source#run_with.
229 230 231 232 233 |
# File 'lib/fiber_stream/flow.rb', line 229 def self.drop_while(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.drop_while(upstream, block) } end |
.filter_map(&block) ⇒ void
This method returns an undefined value.
Creates a transform-and-filter flow.
The block is called once for each upstream element observed by this
stage. Truthy block results are emitted downstream as transformed values;
false and nil results are dropped. Exceptions raised by the block fail the
stream and are re-raised from Source#run_with.
22 23 24 25 26 |
# File 'lib/fiber_stream/flow.rb', line 22 def self.filter_map(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.filter_map(upstream, block) } end |
.grouped(count) ⇒ void
This method returns an undefined value.
Creates a fixed-size grouping flow.
The flow emits arrays containing up to count adjacent upstream elements.
Full groups contain exactly count elements; normal upstream completion
emits one final partial group when one exists. count must be a positive
Integer.
191 192 193 194 195 196 |
# File 'lib/fiber_stream/flow.rb', line 191 def self.grouped(count) raise TypeError, "count must be an Integer" unless count.is_a?(Integer) raise ArgumentError, "count must be positive" unless count.positive? new { |upstream| Pull.grouped(upstream, count) } end |
.lines(chomp: true, max_length: nil) ⇒ Flow[String, String]
Creates a line-splitting flow.
The flow accepts String chunks and emits lines split on "\n". By default
it chomps the trailing newline and one preceding "\r". max_length is an
optional per-line bytesize limit. With max_length: nil, one
unterminated line can buffer without bound. Set a positive max_length
for untrusted, network-facing, or otherwise unbounded streams.
281 282 283 284 285 286 287 288 289 |
# File 'lib/fiber_stream/flow.rb', line 281 def self.lines(chomp: true, max_length: nil) raise TypeError, "chomp must be true or false" unless [true, false].include?(chomp) unless max_length.nil? || max_length.is_a?(Integer) raise TypeError, "max_length must be nil or an Integer" end raise ArgumentError, "max_length must be positive" if max_length&.<= 0 new { |upstream| Pull.lines(upstream, chomp, max_length) } end |
.map(&block) ⇒ void
This method returns an undefined value.
Creates a mapping flow.
The block is called once for each element pulled through this flow.
Exceptions raised by the block fail the stream and are re-raised from
Source#run_with.
10 11 12 13 14 |
# File 'lib/fiber_stream/flow.rb', line 10 def self.map(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.map(upstream, block) } end |
.map_concat(&block) ⇒ void
This method returns an undefined value.
Creates a one-to-many mapping flow.
The block is called once for each upstream element whose expansion is
needed. It must return an object that responds to #each; yielded values
are emitted in order before the next upstream element is pulled.
Exceptions raised by the block or by the returned object's #each fail
the stream and are re-raised from Source#run_with.
43 44 45 46 47 |
# File 'lib/fiber_stream/flow.rb', line 43 def self.map_concat(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.map_concat(upstream, block) } end |
.parallel_map(concurrency:, &block) ⇒ void
This method returns an undefined value.
Creates an ordered scheduler-backed parallel mapping flow.
The stage starts internal scheduled fibers on first downstream demand and
requires an installed Fiber.scheduler in a non-blocking fiber at that
point. At most concurrency mapping blocks run at the same time, and at
most concurrency upstream elements are pulled but not yet emitted downstream.
Results are emitted in input order. Closing the boundary closes upstream
and requests internal worker cancellation. FiberStream does not depend on
Async at runtime.
70 71 72 73 74 75 76 |
# File 'lib/fiber_stream/flow.rb', line 70 def self.parallel_map(concurrency:, &block) raise ArgumentError, "missing block" unless block raise TypeError, "concurrency must be an Integer" unless concurrency.is_a?(Integer) raise ArgumentError, "concurrency must be positive" unless concurrency.positive? new { |upstream| Pull.parallel_map(upstream, concurrency, block) } end |
.parallel_unordered_map(concurrency:, &block) ⇒ void
This method returns an undefined value.
Creates an unordered scheduler-backed parallel mapping flow.
The stage starts internal scheduled fibers on first downstream demand and
requires an installed Fiber.scheduler in a non-blocking fiber at that
point. At most concurrency mapping blocks run at the same time, and at
most concurrency upstream elements are pulled but not yet emitted downstream.
Results are emitted in completion order and input order is not preserved.
Closing the boundary closes upstream and requests internal worker
cancellation. FiberStream does not depend on Async at runtime.
87 88 89 90 91 92 93 |
# File 'lib/fiber_stream/flow.rb', line 87 def self.parallel_unordered_map(concurrency:, &block) raise ArgumentError, "missing block" unless block raise TypeError, "concurrency must be an Integer" unless concurrency.is_a?(Integer) raise ArgumentError, "concurrency must be positive" unless concurrency.positive? new { |upstream| Pull.parallel_unordered_map(upstream, concurrency, block) } end |
.ractor_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) ⇒ void
This method returns an undefined value.
Creates an ordered Ractor-backed mapping flow.
The mapper runs inside worker ractors and must be shareable, typically
created with Ractor.shareable_proc. Results are emitted in input order,
and at most workers upstream elements are pulled but not yet emitted.
input_transfer and output_transfer must be :copy or :move and are
passed to Ractor message sends for element and result transfer.
102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/fiber_stream/flow.rb', line 102 def self.ractor_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) raise ArgumentError, "missing block" unless block raise TypeError, "workers must be an Integer" unless workers.is_a?(Integer) raise ArgumentError, "workers must be positive" unless workers.positive? Internal::RactorTransferPolicy.validate!(:input_transfer, input_transfer) Internal::RactorTransferPolicy.validate!(:output_transfer, output_transfer) raise TypeError, "block must be shareable" unless Ractor.shareable?(block) new { |upstream| Pull.ractor_map(upstream, workers, input_transfer, output_transfer, block) } end |
.ractor_unordered_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) ⇒ void
This method returns an undefined value.
Creates an unordered Ractor-backed mapping flow.
The mapper runs inside worker ractors and must be shareable, typically
created with Ractor.shareable_proc. Results are emitted in worker
completion order, and at most workers upstream elements are pulled but
not yet emitted. input_transfer and output_transfer must be :copy
or :move and are passed to Ractor message sends for element and result
transfer.
122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/fiber_stream/flow.rb', line 122 def self.ractor_unordered_map(workers:, input_transfer: :copy, output_transfer: :copy, &block) raise ArgumentError, "missing block" unless block raise TypeError, "workers must be an Integer" unless workers.is_a?(Integer) raise ArgumentError, "workers must be positive" unless workers.positive? Internal::RactorTransferPolicy.validate!(:input_transfer, input_transfer) Internal::RactorTransferPolicy.validate!(:output_transfer, output_transfer) raise TypeError, "block must be shareable" unless Ractor.shareable?(block) new { |upstream| Pull.ractor_unordered_map(upstream, workers, input_transfer, output_transfer, block) } end |
.reject(&block) ⇒ void
This method returns an undefined value.
Creates a complement filtering flow.
The block is called for upstream elements until it returns false or
nil, or upstream completes. Truthy predicate results drop the original
element; false and nil results pass the element through unchanged.
Exceptions raised by the block fail the stream and are re-raised from
Source#run_with.
153 154 155 156 157 |
# File 'lib/fiber_stream/flow.rb', line 153 def self.reject(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.reject(upstream, block) } end |
.scan(initial, &block) ⇒ void
This method returns an undefined value.
Creates a running-accumulator flow.
The block is called as block.call(accumulator, element) for each
upstream element, matching Sink.fold. The block result becomes the new
accumulator and is emitted downstream. The initial accumulator is not
emitted before the first upstream element.
204 205 206 207 208 |
# File 'lib/fiber_stream/flow.rb', line 204 def self.scan(initial, &block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.scan(upstream, initial, block) } end |
.select(&block) ⇒ void
This method returns an undefined value.
Creates a filtering flow.
The block is called for upstream elements until it returns a truthy value
or upstream completes. Matching elements pass through unchanged.
Exceptions raised by the block fail the stream and are re-raised from
Source#run_with.
140 141 142 143 144 |
# File 'lib/fiber_stream/flow.rb', line 140 def self.select(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.select(upstream, block) } end |
.split(separator, keep_separator: false, max_length: nil) ⇒ Flow[String, String]
Creates a delimiter-splitting flow.
The flow accepts String chunks and emits frames split on the non-empty
String separator. Separator matching is byte-oriented. By default
emitted frames exclude the separator; keep_separator: true preserves it
on separator-terminated frames. max_length is an optional per-frame body
bytesize limit. With max_length: nil, one unterminated frame can buffer
without bound. Set a positive max_length for untrusted, network-facing,
or otherwise unbounded streams.
300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/fiber_stream/flow.rb', line 300 def self.split(separator, keep_separator: false, max_length: nil) raise TypeError, "separator must be String" unless separator.is_a?(String) raise ArgumentError, "separator must not be empty" if separator.empty? raise TypeError, "keep_separator must be true or false" unless [true, false].include?(keep_separator) unless max_length.nil? || max_length.is_a?(Integer) raise TypeError, "max_length must be nil or an Integer" end raise ArgumentError, "max_length must be positive" if max_length&.<= 0 new { |upstream| Pull.split(upstream, separator, keep_separator, max_length) } end |
.take(count) ⇒ void
This method returns an undefined value.
Creates a limiting flow.
The flow emits at most count elements. take(0) completes without
pulling upstream and closes upstream on the first downstream demand. After
the limit is reached, upstream is closed during the pull that forwards
the final element. Negative counts raise ArgumentError; non-Integer
counts raise TypeError.
166 167 168 169 170 171 |
# File 'lib/fiber_stream/flow.rb', line 166 def self.take(count) raise TypeError, "count must be an Integer" unless count.is_a?(Integer) raise ArgumentError, "count must be non-negative" if count.negative? new { |upstream| Pull.take(upstream, count) } end |
.take_while(&block) ⇒ void
This method returns an undefined value.
Creates a predicate-based limiting flow.
The flow emits leading elements while the block result is truthy. The
first false or nil result completes the stream without emitting that
element and closes upstream during the same downstream pull. Exceptions
raised by the block fail the stream and are re-raised from
Source#run_with.
217 218 219 220 221 |
# File 'lib/fiber_stream/flow.rb', line 217 def self.take_while(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.take_while(upstream, block) } end |
.tap(&block) ⇒ void
This method returns an undefined value.
Creates a pass-through observing flow.
The block is called once for each element before that element is emitted
downstream. The block return value is ignored and the original element is
passed through unchanged. Exceptions raised by the block fail the stream
and are re-raised from Source#run_with.
55 56 57 58 59 |
# File 'lib/fiber_stream/flow.rb', line 55 def self.tap(&block) raise ArgumentError, "missing block" unless block new { |upstream| Pull.tap(upstream, block) } end |
.throttle(**options) ⇒ void
This method returns an undefined value.
Creates a scheduler-aware throttling flow.
The rate: form creates a fresh RateLimiter for each materialization.
The limiter: form uses the supplied limiter object, which must respond
to acquire(permits:) and return only after permits are acquired. When
FiberStream-owned waiting is required, the current fiber must be
non-blocking with an installed Fiber.scheduler.
268 269 270 271 272 |
# File 'lib/fiber_stream/flow.rb', line 268 def self.throttle(**) limiter = build_throttle_limiter() new { |upstream| Pull.throttle(upstream, limiter.call) } end |
Instance Method Details
#attach_to(upstream) ⇒ Object
:nodoc:
405 406 407 |
# File 'lib/fiber_stream/flow.rb', line 405 def attach_to(upstream) # :nodoc: @attach.call(upstream) end |
#to(sink) ⇒ void
This method returns an undefined value.
Returns a sink that runs this flow before sink.
The composed sink accepts this flow's input elements and returns the wrapped sink's materialized value. It closes the attached flow chain after normal completion, failure, or early sink completion.
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/fiber_stream/flow.rb', line 376 def to(sink) raise TypeError, "expected FiberStream::Sink" unless sink.is_a?(Sink) Sink.build do |stream| attached_stream = nil primary_error = nil begin attached_stream = attach_to(stream) sink.run_stream(attached_stream) rescue StandardError => error primary_error = error raise ensure begin attached_stream&.close rescue StandardError => close_error raise close_error unless primary_error end end end end |
#via(flow) ⇒ void
This method returns an undefined value.
Returns a reusable flow that applies this flow and then flow.
Construction is lazy. No upstream stream is attached and no elements are pulled until the composed flow is materialized by a source or sink.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/fiber_stream/flow.rb', line 352 def via(flow) raise TypeError, "expected FiberStream::Flow" unless flow.is_a?(Flow) self.class.build do |upstream| attached_stream = attach_to(upstream) begin flow.attach_to(attached_stream) rescue StandardError begin attached_stream.close rescue StandardError nil end raise end end end |