stream

package
v0.3.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 4 Imported by: 0

README

stream

Pull-based data pipeline with composable operators, plus a bounded push fan-out source

stream provides lazy, backpressure-aware data processing for Go. Pipelines pull values on demand — no work happens until you call Collect, Drain, or ForEach. This design naturally handles flow control without buffering or blocking. For the genuinely push-shaped "one source, many observers" case, Broadcaster fans events out to independent subscribers with bounded, drop-on-overflow buffers.

gokit converges on the rskit-stream operator vocabulary (map/filter/fan_out/window/batch/parallel/merge/partition/…) and its Broadcaster, expressed idiomatically in Go: a pull iterator for transformation pipelines and a bounded channel bus for fan-out. No operator buffers without bound.

Features

  • Lazy evaluation — operators compose but don't execute until pulled
  • Backpressure — upstream producers only generate values when downstream consumers request them
  • Composable operators — map, filter, batch, throttle, window, and more
  • Provider integration — structurally compatible with provider.Iterator[T]
  • Context-aware — all operations support cancellation and deadlines
  • Type-safe — full generic support for strongly typed pipelines

Install

go get github.com/kbukum/gokit@latest

Pipeline is part of the core module — no separate sub-module import needed.

Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/kbukum/gokit/stream"
)

func main() {
    ctx := context.Background()
    
    // Create a pipeline from a slice
    src := stream.FromSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
    
    // Double each value
    doubled := stream.Map(src, func(_ context.Context, n int) (int, error) {
        return n * 2, nil
    })
    
    // Keep only even numbers
    evens := stream.Filter(doubled, func(n int) bool {
        return n%2 == 0
    })
    
    // Collect results
    results, err := stream.Collect(ctx, evens)
    if err != nil {
        panic(err)
    }
    
    fmt.Println(results) // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
}

Operators

Operator reference
Canonical operator gokit symbol Notes
map stream.Map Lazy, ordered one-to-one transform.
filter stream.Filter Pulls until a value matches the predicate.
batch stream.Batch Emits by size or timeout.
window stream.TumblingWindow Fixed-duration non-overlapping windows.
sliding stream.SlidingWindow Time-based overlapping windows.
fan_out stream.FanOut Runs multiple functions for each input and emits []O.
parallel stream.Parallel Concurrent map; output order is not preserved.
merge stream.Merge Concurrently merges multiple pipelines.
partition stream.Partition Streaming bounded tee into matching/rejected branches.
throttle stream.Throttle Drops values that arrive before the interval elapses.
debounce stream.Debounce Emits the latest value after a quiet period.
distinct stream.Distinct Removes duplicate comparable values.
take stream.Take Emits at most the first n values.
skip stream.Skip Ignores the first n values.
buffer stream.Buffer Bounded channel between stages; size <= 0 becomes 1.
broadcaster stream.Broadcaster Bounded push fan-out to many subscribers; drops overflow per subscriber.
Synchronous Operators
Operator Description
Map[I, O](p *Pipeline[I], fn func(context.Context, I) (O, error)) *Pipeline[O] Transform each value
FlatMap[I, O](p *Pipeline[I], fn func(context.Context, I) (Iterator[O], error)) *Pipeline[O] Transform each value into multiple values
Filter[T](p *Pipeline[T], fn func(T) bool) *Pipeline[T] Keep values matching predicate
Tap[T](p *Pipeline[T], fn func(context.Context, T) error) *Pipeline[T] Side-effect without altering value (logging, metrics)
TapEach[T](p *Pipeline[[]T], fns ...func(context.Context, T) error) *Pipeline[[]T] Per-element side-effect on slices
FanOut[I, O](p *Pipeline[I], fns ...func(context.Context, I) (O, error)) *Pipeline[[]O] Apply multiple functions in parallel, collect as slice
Reduce[T, R](p *Pipeline[T], init R, fn func(R, T) R) *Pipeline[R] Accumulate all values into one result
Concat[T](pipelines ...*Pipeline[T]) *Pipeline[T] Join pipelines sequentially
Concurrent Operators
Operator Description
Buffer[T](p *Pipeline[T], size int) *Pipeline[T] Decouple producer/consumer with buffered channel
Parallel[I, O](p *Pipeline[I], n int, fn func(context.Context, I) (O, error)) *Pipeline[O] Concurrent Map with worker pool (order NOT preserved)
Merge[T](pipelines ...*Pipeline[T]) *Pipeline[T] Combine pipelines concurrently (order NOT preserved)
Push Fan-Out (Broadcaster)

For the "watch one source → fan a typed change stream out to many independent observers" shape (config reloads, service discovery, cache invalidation, secret rotation), use Broadcaster[T]. Each subscriber owns a private bounded channel: a subscriber lagging beyond its buffer drops the overflow (backpressure by drop) but never blocks the broadcaster or its peers.

Operator Description
NewBroadcaster[T](opts ...BroadcasterOption) *Broadcaster[T] Create a fan-out bus (default buffer DefaultBroadcastBuffer = 64)
WithBroadcastBuffer(size int) BroadcasterOption Set per-subscriber buffer (clamped to >= 1)
(*Broadcaster[T]).Subscribe(ctx) <-chan T Register a subscriber; channel closes on ctx cancel or Close
(*Broadcaster[T]).Broadcast(item T) Deliver to all live subscribers; full subscribers drop the event
(*Broadcaster[T]).Close() Terminate all subscribers and release their goroutines (idempotent)
(*Broadcaster[T]).SubscriberCount() int / Buffer() int Live subscriber count / per-subscriber buffer
b := stream.NewBroadcaster[Config](stream.WithBroadcastBuffer(16))
defer b.Close()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
updates := b.Subscribe(ctx)
go func() {
    for cfg := range updates { // ends when ctx is canceled or b.Close() is called
        reload(cfg)
    }
}()

b.Broadcast(newConfig) // reaches every live subscriber; slow ones drop overflow
Stream/Time-Aware Operators
Operator Description
Throttle[T](p *Pipeline[T], interval time.Duration) *Pipeline[T] Rate-limit values (drop values arriving faster than interval)
Batch[T](p *Pipeline[T], size int, timeout time.Duration) *Pipeline[[]T] Collect N items or wait timeout, emit as slice
Debounce[T](p *Pipeline[T], duration time.Duration) *Pipeline[T] Wait for silence before emitting latest value
TumblingWindow[T](p *Pipeline[T], duration time.Duration) *Pipeline[[]T] Non-overlapping fixed-duration windows
SlidingWindow[T](p *Pipeline[T], timeFn func(T) time.Time, windowSize, slideBy time.Duration) *Pipeline[[]T] Overlapping event-time windows with configurable slide
Terminal Operators
Operator Description
Collect[T](ctx context.Context, p *Pipeline[T]) ([]T, error) Pull all values into a slice
Drain[T](p *Pipeline[T], sink func(context.Context, T) error) *Runnable Pull values and pass to a sink, discard results
ForEach[T](ctx context.Context, p *Pipeline[T], fn func(context.Context, T) error) error Apply function to each value
Source Constructors
Constructor Description
FromSlice[T](items []T) *Pipeline[T] Create pipeline from slice
From[T](iter Iterator[T]) *Pipeline[T] Create pipeline from any iterator (including provider.Iterator[T])
FromFunc[T](fn func(context.Context) Iterator[T]) *Pipeline[T] Create pipeline from an iterator-producing function

Usage Examples

Example 1: Data Transformation
ctx := context.Background()

// Process a list of user IDs
userIDs := stream.FromSlice([]string{"u1", "u2", "u3", "u4", "u5"})

// Fetch user data
users := stream.Map(userIDs, func(ctx context.Context, id string) (*User, error) {
    return userService.GetByID(ctx, id)
})

// Filter active users
active := stream.Filter(users, func(u *User) bool {
    return u.Active
})

// Collect results
activeUsers, err := stream.Collect(ctx, active)
Example 2: Batching & Throttling
// Stream of events
events := stream.From(eventSource) // eventSource implements Iterator[Event]

// Rate-limit to 10 events/sec
throttled := stream.Throttle(events, 100*time.Millisecond)

// Batch into groups of 50 or every 5 seconds
batched := stream.Batch(throttled, 50, 5*time.Second)

// Process each batch
stream.Drain(batched, func(ctx context.Context, batch []Event) error {
    return batchProcessor.Process(ctx, batch)
}).Run(ctx)
Example 3: Provider Integration
import (
    "github.com/kbukum/gokit/stream"
    "github.com/kbukum/gokit/provider"
)

// Assume audioSource implements provider.Iterator[AudioChunk]
src := stream.From(audioSource)

// Transcribe audio chunks
transcribed := stream.FlatMap(src, func(ctx context.Context, chunk AudioChunk) (Iterator[Segment], error) {
    return transcriber.Execute(ctx, chunk)
})

// Publish to Kafka as side-effect
tapped := stream.Tap(transcribed, func(ctx context.Context, seg Segment) error {
    return kafkaPublisher.Send(ctx, seg)
})

// Identify speakers
identified := stream.Map(tapped, func(ctx context.Context, seg Segment) (IdentifiedSegment, error) {
    return speakerID.Execute(ctx, seg)
})

// Drain to final sink
stream.Drain(identified, finalSink.Send).Run(ctx)
Example 4: Windowing
// Stream of metrics
metrics := stream.From(metricSource) // metricSource implements Iterator[Metric]

// Create 1-minute tumbling windows
windows := stream.TumblingWindow(metrics, 1*time.Minute)

// Aggregate each window
aggregated := stream.Map(windows, func(ctx context.Context, window []Metric) (Summary, error) {
    return aggregator.Summarize(window), nil
})

// Store summaries
stream.Drain(aggregated, summaryStore.Save).Run(ctx)
Example 5: FanOut (Parallel Processing)
// Process data through multiple models concurrently
src := stream.FromSlice(audioChunks)

results := stream.FanOut(src,
    modelA.Execute, // Run in parallel
    modelB.Execute,
    modelC.Execute,
)

// Each value is now []Result containing outputs from all 3 models
stream.Drain(results, func(ctx context.Context, outputs []Result) error {
    return combiner.Merge(ctx, outputs)
}).Run(ctx)
Example 6: Error Handling
src := stream.FromSlice(items)

processed := stream.Map(src, func(ctx context.Context, item Item) (Result, error) {
    // Errors propagate and halt the pipeline
    if err := validate(item); err != nil {
        return Result{}, fmt.Errorf("validation failed: %w", err)
    }
    return process(item), nil
})

// Collect will return the first error encountered
results, err := stream.Collect(ctx, processed)
if err != nil {
    log.Error("pipeline failed", map[string]interface{}{"error": err})
    return
}
Example 7: Parallel Processing with Workers
// Process 100 items with 10 concurrent workers
src := stream.FromSlice(items)

// Order is NOT preserved with Parallel
processed := stream.Parallel(src, 10, func(ctx context.Context, item Item) (Result, error) {
    return heavyProcessing(ctx, item)
})

results, err := stream.Collect(ctx, processed)

Design Philosophy

Pull vs Push

Pull-based (this library):

  • Downstream pulls from upstream
  • Natural backpressure — producer only works when consumer is ready
  • No buffering required
  • Lazy evaluation — nothing happens until terminal operator runs

Push-based (channels, reactive streams):

  • Upstream pushes to downstream
  • Requires explicit backpressure mechanism
  • Buffering often needed
  • Eager evaluation — producers start immediately
Structural Compatibility with Provider

The Iterator[T] interface is structurally identical to provider.Iterator[T]:

type Iterator[T any] interface {
    Next(ctx context.Context) (T, bool, error)
    Close() error
}

This means provider streams plug directly into pipelines:

// Provider iterator
audioIterator := audioProvider.Stream(ctx, input)

// Wrap in pipeline
p := stream.From(audioIterator)

// Apply operators
transcribed := stream.FlatMap(p, transcriber.Execute)

Testing

Use FromSlice for deterministic test data:

func TestPipeline(t *testing.T) {
    ctx := context.Background()
    src := stream.FromSlice([]int{1, 2, 3})
    
    doubled := stream.Map(src, func(_ context.Context, n int) (int, error) {
        return n * 2, nil
    })
    
    result, err := stream.Collect(ctx, doubled)
    require.NoError(t, err)
    assert.Equal(t, []int{2, 4, 6}, result)
}

Performance Considerations

  • Lazy evaluation means no work until terminal operator runs
  • Synchronous operators (Map, Filter) add negligible overhead
  • Concurrent operators (Parallel, Buffer, Merge) spawn goroutines — use when I/O or CPU-heavy work benefits from parallelism
  • Throttle/Batch/Debounce use timers — appropriate for real-time/streaming scenarios, not batch processing
  • Context cancellation stops the pipeline immediately at the next operator
  • provider — Provider pattern with Iterator interface (structurally compatible)
  • dag — Dependency-ordered task orchestration with batch/stream modes
  • sse — Server-sent events broadcasting (push-based, not pull-based)

License

MIT — Copyright (c) 2024 kbukum

Documentation

Overview

Package stream provides composable, pull-based data stream operators plus a bounded push fan-out source.

Canonical shape

gokit converges on the rskit-stream operator vocabulary (map/filter/fan_out/window/batch/parallel/merge/partition/…) but keeps an idiomatic Go pull-iterator model for transformation pipelines: pipelines are lazy — no work happens until values are pulled via Collect, Drain, or ForEach, and each stage pulls from the previous stage on demand. Pull gives natural, allocation-free backpressure without an explicit flow-control protocol.

The genuinely push-shaped concern — fanning one source out to many independent observers — is served by Broadcaster, mirroring rskit's Broadcaster<T>. Every subscriber owns a private bounded channel; a subscriber that lags beyond its buffer drops overflow (backpressure by drop) but never blocks the broadcaster or its peers.

Bounded buffers

No operator buffers without bound. Buffer clamps size <= 0 to 1; Broadcaster clamps its per-subscriber buffer to at least 1; the time/size-aware operators (Batch, TumblingWindow, SlidingWindow) emit and release each group as it completes. Concurrent operators (Parallel, Merge, Buffer) run owned goroutines bounded by ctx cancellation and closed via the iterator's Close.

The Iterator interface is structurally compatible with provider.Iterator[T], so provider streams plug directly into pipelines.

Operators

Synchronous (single-goroutine):

  • Map: transform each value
  • FlatMap: transform each value into multiple values
  • Filter: keep values matching a predicate
  • Tap: side-effect without altering the value (logging, metrics, mid-pipeline publish)
  • TapEach: per-element side-effect on []T (e.g., after FanOut)
  • FanOut: apply multiple functions in parallel, collect results as []O
  • Reduce: accumulate all values into one result
  • Concat: join pipelines sequentially

Concurrent (multi-goroutine):

  • Buffer: decouple producer/consumer with a buffered channel
  • Parallel: concurrent Map with a worker pool (order NOT preserved)
  • Merge: combine multiple pipelines concurrently (order NOT preserved)

Stream/time-aware:

  • Throttle: rate-limit values (drop values arriving faster than interval)
  • Batch: collect N items or wait timeout, emit as slice
  • Debounce: wait for silence before emitting the latest value
  • TumblingWindow: non-overlapping fixed-duration windows
  • SlidingWindow: overlapping windows with configurable slide

Push fan-out:

  • Broadcaster: bounded, cancellable one-to-many event fan-out (drop overflow)

Usage

src := stream.FromSlice([]int{1, 2, 3, 4, 5})
doubled := stream.Map(src, func(_ context.Context, n int) (int, error) {
    return n * 2, nil
})
evens := stream.Filter(doubled, func(n int) bool { return n%2 == 0 })
results, _ := stream.Collect(ctx, evens)

With providers:

src := stream.From(audioSource)
transcribed := stream.FlatMap(src, transcriber.Execute)
tapped := stream.Tap(transcribed, kafkaPublish.Send)
identified := stream.Map(tapped, speakerID.Execute)
stream.Drain(identified, finalSink.Send).Run(ctx)

Index

Examples

Constants

View Source
const DefaultBroadcastBuffer = 64

DefaultBroadcastBuffer is the per-subscriber buffer used when no buffer size is configured. A subscriber lagging more than this many unconsumed events drops the overflow rather than stalling the broadcaster.

Variables

This section is empty.

Functions

func Collect

func Collect[T any](ctx context.Context, p *Pipeline[T]) ([]T, error)

Collect runs the pipeline and returns all values as a slice.

func ForEach

func ForEach[T any](ctx context.Context, p *Pipeline[T], fn func(context.Context, T) error) error

ForEach pulls all values and calls fn for each. Convenience wrapper around Drain.

func Partition

func Partition[T any](p *Pipeline[T], predicate func(T) bool) (matching *Pipeline[T], rejected *Pipeline[T])

Partition splits a pipeline into two streaming branches using predicate. The upstream is consumed once by a bounded tee. Both branches should be consumed concurrently; closing one branch drops values routed to it while the other branch continues.

Types

type Broadcaster

type Broadcaster[T any] struct {
	// contains filtered or unexported fields
}

Broadcaster is a bounded, cancellable fan-out source: it turns "observe a backend" into a bounded stream of typed events delivered to many independent subscribers. Each subscriber owns a private bounded channel — a subscriber that falls further behind than the buffer loses interim events (backpressure by drop) but never blocks the broadcaster or its peers. This is the canonical owner for the "watch a source → typed change stream" shape that recurs across config reloads, service discovery, cache invalidation, and secret rotation.

Share a Broadcaster by pointer; every holder observes the same subscriber set. It is safe for concurrent use. NewBroadcaster is the canonical constructor, but the zero value is also usable: it lazily initializes on first use with the default buffer.

func NewBroadcaster

func NewBroadcaster[T any](opts ...BroadcasterOption) *Broadcaster[T]

NewBroadcaster creates a Broadcaster with the given options. Without WithBroadcastBuffer it uses DefaultBroadcastBuffer.

func (*Broadcaster[T]) Broadcast

func (b *Broadcaster[T]) Broadcast(item T)

Broadcast delivers item to every live subscriber. Delivery to a full subscriber is dropped rather than blocked, so a slow subscriber never stalls the broadcaster or its peers. Broadcasting after Close is a no-op.

func (*Broadcaster[T]) Buffer

func (b *Broadcaster[T]) Buffer() int

Buffer returns the effective per-subscriber buffer size.

func (*Broadcaster[T]) Close

func (b *Broadcaster[T]) Close()

Close terminates the Broadcaster: every subscriber channel is closed and all subscriber goroutines are released. It is idempotent, and further Broadcast calls become no-ops.

func (*Broadcaster[T]) Subscribe

func (b *Broadcaster[T]) Subscribe(ctx context.Context) <-chan T

Subscribe registers a new subscriber and returns its receive-only event channel. The channel is closed — terminating any range over it — when ctx is canceled or the Broadcaster is closed. Subscribing with an already-canceled context, or to a closed Broadcaster, returns an already-closed channel without registering a subscriber or spawning a watcher goroutine.

func (*Broadcaster[T]) SubscriberCount

func (b *Broadcaster[T]) SubscriberCount() int

SubscriberCount returns the number of currently live subscribers.

type BroadcasterOption

type BroadcasterOption func(*broadcasterConfig)

BroadcasterOption configures a Broadcaster at construction time.

func WithBroadcastBuffer

func WithBroadcastBuffer(size int) BroadcasterOption

WithBroadcastBuffer sets the per-subscriber buffer size. Values below 1 are clamped to 1 so every subscriber can hold at least one in-flight event.

type Executor

type Executor[T any] struct {
	// contains filtered or unexported fields
}

Executor runs a sequence of steps with progress reporting.

func NewExecutor

func NewExecutor[T any](steps []Step[T], opts ...ExecutorOption) *Executor[T]

NewExecutor creates an Executor for the given steps.

func (*Executor[T]) Execute

func (e *Executor[T]) Execute(ctx context.Context, input T, onProgress func(StepProgress[T])) (T, error)

Execute runs all steps sequentially, calling onProgress for each step's lifecycle events. Returns the final output or the first error encountered. Supports context cancellation between steps.

type ExecutorOption

type ExecutorOption func(*executorConfig)

ExecutorOption configures an Executor.

type Iterator

type Iterator[T any] interface {
	// Next returns the next value. Returns (zero, false, nil) when exhausted.
	Next(ctx context.Context) (T, bool, error)
	// Close releases any resources held by the iterator.
	Close() error
}

Iterator provides pull-based sequential access to a stream of values. Structurally compatible with provider.Iterator[T].

type Pipeline

type Pipeline[T any] struct {
	// contains filtered or unexported fields
}

Pipeline represents a lazy, pull-based data pipeline. No work happens until values are pulled via Collect, Drain, or ForEach.

func Batch

func Batch[T any](p *Pipeline[T], size int, timeout time.Duration) *Pipeline[[]T]

Batch collects up to size values or waits timeout (whichever comes first), then emits them as a slice.

size=0 means collect until timeout. timeout=0 means collect until size. Both zero is invalid and defaults to size=1.

Named Batch (not Buffer) because pipeline.Buffer already exists for channel-based decoupling between stages.

func Buffer

func Buffer[T any](p *Pipeline[T], size int) *Pipeline[T]

Buffer adds a buffered channel between pipeline stages. This decouples the production rate from the consumption rate.

func Concat

func Concat[T any](pipelines ...*Pipeline[T]) *Pipeline[T]

Concat joins multiple pipelines sequentially. All values from the first pipeline are yielded before the second, etc.

func Debounce

func Debounce[T any](p *Pipeline[T], duration time.Duration) *Pipeline[T]

Debounce waits for silence of the given duration after the last value before emitting. If a new value arrives during the quiet period, the timer resets and only the latest value is emitted.

Useful for "wait until input stops" patterns (e.g., search-as-you-type, batching rapid events).

func Distinct

func Distinct[T comparable](p *Pipeline[T]) *Pipeline[T]

Distinct removes duplicate comparable values while preserving first-seen order.

func FanOut

func FanOut[I, O any](p *Pipeline[I], fns ...func(context.Context, I) (O, error)) *Pipeline[[]O]

FanOut applies multiple functions to each input value in parallel and collects all results as a slice.

func Filter

func Filter[T any](p *Pipeline[T], fn func(T) bool) *Pipeline[T]

Filter keeps only values that satisfy the predicate.

Example
package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/kbukum/gokit/stream"
)

func main() {
	p := stream.FromSlice([]string{"alpha", "", "beta", ""})
	nonEmpty := stream.Filter(p, func(s string) bool { return s != "" })
	got, _ := stream.Collect(context.Background(), nonEmpty)
	fmt.Println(strings.Join(got, ","))
}
Output:
alpha,beta

func FlatMap

func FlatMap[I, O any](p *Pipeline[I], fn func(context.Context, I) (Iterator[O], error)) *Pipeline[O]

FlatMap transforms each value into an iterator and flattens the results.

func From

func From[T any](iter Iterator[T]) *Pipeline[T]

From creates a pipeline from an existing Iterator.

func FromFunc

func FromFunc[T any](fn func(ctx context.Context) Iterator[T]) *Pipeline[T]

FromFunc creates a pipeline from a factory that produces an Iterator.

func FromSlice

func FromSlice[T any](items []T) *Pipeline[T]

FromSlice creates a pipeline from a slice of values.

Example
package main

import (
	"context"
	"fmt"

	"github.com/kbukum/gokit/stream"
)

func main() {
	p := stream.FromSlice([]int{1, 2, 3, 4})
	doubled := stream.Map(p, func(_ context.Context, x int) (int, error) {
		return x * 2, nil
	})
	got, err := stream.Collect(context.Background(), doubled)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(got)
}
Output:
[2 4 6 8]

func Map

func Map[I, O any](p *Pipeline[I], fn func(context.Context, I) (O, error)) *Pipeline[O]

Map transforms each value using fn.

func Merge

func Merge[T any](pipelines ...*Pipeline[T]) *Pipeline[T]

Merge combines multiple pipelines concurrently. Values are yielded as they become available from any source. Order is NOT preserved.

func Parallel

func Parallel[I, O any](p *Pipeline[I], n int, fn func(context.Context, I) (O, error)) *Pipeline[O]

Parallel applies fn to each value concurrently with up to n workers. Order is NOT preserved. Use Map for ordered processing.

func Reduce

func Reduce[T, R any](p *Pipeline[T], init R, fn func(R, T) R) *Pipeline[R]

Reduce accumulates all values into a single result. The pipeline yields exactly one value: the final accumulator.

func Skip

func Skip[T any](p *Pipeline[T], n int) *Pipeline[T]

Skip ignores the first n values from the pipeline.

func SlidingWindow

func SlidingWindow[T any](p *Pipeline[T], timeFn func(T) time.Time, windowSize, slideBy time.Duration) *Pipeline[[]T]

SlidingWindow emits overlapping windows based on a time extraction function. windowSize is the duration of each window. slideBy is how far each window advances.

Values must arrive in time order. Each emitted slice contains all values whose timestamp falls within [windowStart, windowStart+windowSize).

func Take

func Take[T any](p *Pipeline[T], n int) *Pipeline[T]

Take yields at most the first n values from the pipeline.

func Tap

func Tap[T any](p *Pipeline[T], fn func(context.Context, T) error) *Pipeline[T]

Tap calls fn as a side-effect for each value, then passes the value through unchanged. Use for logging, metrics, or mid-pipeline publishing.

func TapEach

func TapEach[T any](p *Pipeline[[]T], fns ...func(context.Context, T) error) *Pipeline[[]T]

TapEach applies fn[i] to each element of a []T slice as a side-effect, then passes the slice through unchanged. Useful after FanOut.

func Throttle

func Throttle[T any](p *Pipeline[T], interval time.Duration) *Pipeline[T]

Throttle drops values that arrive faster than the given interval. Only the first value in each interval window is emitted; subsequent values within the same window are dropped. Useful for rate-limiting downstream processing.

func TumblingWindow

func TumblingWindow[T any](p *Pipeline[T], duration time.Duration) *Pipeline[[]T]

TumblingWindow groups values into non-overlapping fixed-duration windows. Each window is emitted as a slice when its duration expires. The final partial window is emitted when the source is exhausted.

func (*Pipeline[T]) Iter

func (p *Pipeline[T]) Iter(ctx context.Context) Iterator[T]

Iter returns the raw Iterator for this pipeline. The caller must Close() it.

type Runnable

type Runnable struct {
	// contains filtered or unexported fields
}

Runnable is a fully-configured pipeline ready to execute.

func Drain

func Drain[T any](p *Pipeline[T], sink func(context.Context, T) error) *Runnable

Drain creates a Runnable that pulls all values and sends each to sink.

func (*Runnable) Run

func (r *Runnable) Run(ctx context.Context) error

Run executes the pipeline until completion or context cancellation.

type Step

type Step[T any] struct {
	ID      string
	Name    string
	Execute func(ctx context.Context, input T) (T, error)
	Skip    func(ctx context.Context, input T) bool // optional: skip if true
}

Step represents a named unit of work in an executable pipeline.

type StepProgress

type StepProgress[T any] struct {
	StepID string         `json:"step_id"`
	Name   string         `json:"name"`
	Status StepStatus     `json:"status"`
	Result *StepResult[T] `json:"result,omitempty"`
}

StepProgress reports progress for a single step in pipeline execution.

type StepResult

type StepResult[T any] struct {
	StepID  string        `json:"step_id"`
	Output  T             `json:"output,omitempty"`
	Err     error         `json:"error,omitempty"`
	Elapsed time.Duration `json:"elapsed"`
}

StepResult captures the outcome of a step execution.

type StepStatus

type StepStatus string

StepStatus describes the status of a step during execution.

const (
	StepStarted   StepStatus = "started"
	StepCompleted StepStatus = "completed"
	StepFailed    StepStatus = "failed"
	StepSkipped   StepStatus = "skipped"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL