Repository: ahmetb/go-linq

Issue:
how about add parameter timeout for FromChannelT？


Discussion:

Solution patch:
diff --git a/README.md b/README.md
index 979a7c0..758742c 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,7 @@ This will make your code free of `any` and type assertions.
 ```go
 var results []string
 
-From(sentences).
+FromSlice(sentences).
 	// split sentences to words
 	SelectManyT(func(sentence string) Query {
 		return From(strings.Split(sentence, " "))
@@ -183,6 +183,7 @@ Available constructors:
 - `FromSlice` - creates a query from a slice
 - `FromMap` - creates a query from a map
 - `FromChannel` - creates a query from a channel
+- `FromChannelWithContext` - creates a query from a channel with `Context` support
 - `FromString` - creates a query from a string (iterating over runes)
 - `FromIterable` - creates a query from a custom collection implementing the `Iterable` interface
 
@@ -194,7 +195,8 @@ significantly less efficient. For all new code, it’s recommended to use the ex
 ```text
 v4.0.0 (2025-10-12)
 * Breaking change: Migrated to standard Go iterator pattern. (thanks @kalaninja!)
-* Added typed constructors: FromSlice(), FromMap(), FromChannel(), FromString().
+* Added typed constructors: FromSlice(), FromMap(), FromChannel(),
+ FromChannelWithContext(), FromString().
 * Breaking change: Removed FromChannelT() in favor of FromChannel().
 
 v3.2.0 (2020-12-29)
diff --git a/from.go b/from.go
index 3b94fdf..1dcc7eb 100644
--- a/from.go
+++ b/from.go
@@ -1,6 +1,7 @@
 package linq
 
 import (
+	"context"
 	"fmt"
 	"iter"
 	"reflect"
@@ -68,6 +69,31 @@ func FromChannel[T any](source <-chan T) Query {
 	}
 }
 
+// FromChannelWithContext initializes a linq query with a passed channel
+// and stops iterating either when the channel is closed or when the context is canceled.
+func FromChannelWithContext[T any](ctx context.Context, source <-chan T) Query {
+	return Query{
+		Iterate: func(yield func(any) bool) {
+			for {
+				select {
+				case <-ctx.Done():
+					// Context canceled or deadline exceeded
+					return
+				case item, ok := <-source:
+					if !ok {
+						// Channel closed
+						return
+					}
+					if !yield(item) {
+						// Consumer stopped early
+						return
+					}
+				}
+			}
+		},
+	}
+}
+
 // FromString initializes a query from a string, iterating over its runes.
 func FromString[S ~string](source S) Query {
 	return Query{



Test patch:
diff --git a/from_test.go b/from_test.go
index 0e02f74..beffabd 100644
--- a/from_test.go
+++ b/from_test.go
@@ -1,6 +1,10 @@
 package linq
 
-import "testing"
+import (
+	"context"
+	"testing"
+	"time"
+)
 
 func TestFromSlice(t *testing.T) {
 	s := [3]int{1, 2, 3}
@@ -34,6 +38,39 @@ func TestFromChannel(t *testing.T) {
 	}
 }
 
+func TestFromChannelWithContext_Cancel(t *testing.T) {
+	c := make(chan int, 3)
+	defer close(c)
+	c <- 10
+	c <- 15
+	c <- -3
+
+	w := []any{10, 15, -3}
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+
+	if q := FromChannelWithContext(ctx, c); !validateQuery(q, w) {
+		t.Errorf("FromChannelWithContext() failed expected %v", w)
+	}
+}
+
+func TestFromChannelWithContext_Closed(t *testing.T) {
+	c := make(chan int, 3)
+	c <- 10
+	c <- 15
+	c <- -3
+	close(c)
+
+	w := []any{10, 15, -3}
+
+	ctx := context.Background()
+
+	if q := FromChannelWithContext(ctx, c); !validateQuery(q, w) {
+		t.Errorf("FromChannelWithContext() failed expected %v", w)
+	}
+}
+
 func TestFromString(t *testing.T) {
 	s := "string"
 	w := []any{'s', 't', 'r', 'i', 'n', 'g'}