Cursor 0.0.3
See the version list below for details.
dotnet add package Cursor --version 0.0.3
NuGet\Install-Package Cursor -Version 0.0.3
<PackageReference Include="Cursor" Version="0.0.3" />
<PackageVersion Include="Cursor" Version="0.0.3" />
<PackageReference Include="Cursor" />
paket add Cursor --version 0.0.3
#r "nuget: Cursor, 0.0.3"
#:package Cursor@0.0.3
#addin nuget:?package=Cursor&version=0.0.3
#tool nuget:?package=Cursor&version=0.0.3
Cursor Pagination Library
A C# source generator library that automatically creates async enumeration methods for cursor and offset-based paginated APIs, making it effortless to iterate through paginated results.
Features
- ✨ Automatic Code Generation: Uses C# source generators to create enumeration methods at compile-time
- 🚀 Zero Runtime Overhead: All code is generated during compilation
- 🔄 IAsyncEnumerable Support: Modern async streaming with
await foreach - 🎯 Type-Safe: Full IntelliSense support and compile-time checking
- 🔌 API Agnostic: Works with any HTTP client library (Refit, HttpClient, etc.)
- ⚙️ Customizable: Configure parameter names to match your API conventions
- 📦 AOT Compatible: Fully compatible with Native AOT compilation
Installation
dotnet add package Cursor
Quick Start
1. Define Your Cursor Page Model
Implement the ICursorPage<T> interface for your paginated response:
using Cursor;
public class CursorPage<T> : ICursorPage<T>
{
public List<T> Items { get; set; } = new();
public string? NextCursor { get; set; }
}
2. Decorate Your API Methods
Add the [GenerateEnumerator] attribute to methods that return paginated results:
using Cursor;
using Refit;
public interface IExampleApi
{
[Get("/items")]
[GenerateEnumerator]
Task<CursorPage<Item>> ListItemsAsync(
int? limit = null,
string? cursor = null,
CancellationToken cancellationToken = default
);
}
3. Use the Generated Enumerators
The source generator automatically creates extension methods for you:
var api = RestService.For<IExampleApi>("https://api.example.com");
// Enumerate all items across all pages
await foreach (var item in api.EnumerateItemsAsync())
{
Console.WriteLine(item);
}
// Or collect all items at once
var allItems = await api.EnumerateItemsAsync().ToListAsync();
// Enumerate pages instead of individual items
await foreach (var page in api.EnumerateItemsPagesAsync())
{
Console.WriteLine($"Page with {page.Items.Count} items");
}
Requirements
Your API methods must:
- Return
Task<TPage>whereTPageimplementsICursorPage<T> - Follow the naming pattern
List*Async(e.g.,ListItemsAsync,ListUsersAsync) - Have parameters for:
limit(or custom name) - page sizecursor(or custom name) - pagination tokencancellationToken- for async cancellation
Advanced Usage
Custom Parameter Names
If your API uses different parameter names, you can customize them:
[Get("/items")]
[GenerateEnumerator(
LimitParameterName = "pageSize",
CursorParameterName = "nextToken"
)]
Task<CursorPage<Item>> ListItemsAsync(
int? pageSize,
string? nextToken,
CancellationToken cancellationToken
);
[Get("/items")]
[GenerateEnumerator(
CursorParameterName = "offset"
)]
Task<CursorPage<Item>> ListItemsAsync(
int? offset,
int? limit,
CancellationToken cancellationToken
);
Custom Cursor Page Implementation
You can use any class that implements ICursorPage<T>:
public record CustomCursorPage<T> : ICursorPage<T>
{
public required List<T> Data { get; init; }
public string? Cursor { get; init; }
List<T> ICursorPage<T>.Items => Data;
string? ICursorPage<T>.NextCursor => Cursor;
}
[Get("/items")]
[GenerateEnumerator]
Task<CustomCursorPage<Item>> ListItemsAsync(
int? limit,
string? cursor = null,
CancellationToken cancellationToken = default
);
Passing Additional Parameters
The generator preserves any additional parameters in the generated methods:
[Get("/items")]
[GenerateEnumerator]
Task<CursorPage<Item>> ListItemsAsync(
string category, // Additional parameter
int? limit = null,
string? cursor = null,
CancellationToken cancellationToken = default
);
// Usage
await foreach (var item in api.EnumerateItemsAsync("electronics"))
{
// Process items from "electronics" category
}
Custom Attributes
You can create your own attribute that inherits from GenerateEnumeratorAttribute:
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class MyCustomEnumeratorAttribute : GenerateEnumeratorAttribute
{
public MyCustomEnumeratorAttribute()
{
LimitParameterName = "pageSize";
CursorParameterName = "nextToken";
}
}
// Usage
[Get("/items")]
[MyCustomEnumerator]
Task<CursorPage<Item>> ListItemsAsync(
int? pageSize,
string? nextToken,
CancellationToken cancellationToken
);
Generated Code
For a method named ListItemsAsync, the generator creates:
EnumerateItemsAsync()- ReturnsIAsyncEnumerable<T>for iterating individual itemsEnumerateItemsPagesAsync()- ReturnsIAsyncEnumerable<ICursorPage<T>>for iterating pages
Both methods:
- Automatically handle pagination by following the
NextCursor - Support
CancellationTokenfor graceful cancellation - Allow configuring the page size via an optional
pageSizeparameter
How It Works
The library uses Roslyn source generators to analyze your code at compile-time:
- Finds methods decorated with
[GenerateEnumerator]or derived attributes - Validates the method signature and return type
- Generates extension methods that handle the pagination loop
- The generated code is added to your compilation automatically
No reflection or runtime code generation is involved, making it AOT-compatible and performant.
Examples
Example 1: Basic Usage with Refit
using Cursor;
using Refit;
public interface IGitHubApi
{
[Get("/users/{user}/repos")]
[GenerateEnumerator]
Task<CursorPage<Repository>> ListRepositoriesAsync(
string user,
int? limit = null,
string? cursor = null,
CancellationToken cancellationToken = default
);
}
// Usage
var api = RestService.For<IGitHubApi>("https://api.github.com");
await foreach (var repo in api.EnumerateRepositoriesAsync("octocat"))
{
Console.WriteLine($"{repo.Name}: {repo.Description}");
}
Example 2: Processing Pages
await foreach (var page in api.EnumerateRepositoriesPagesAsync("octocat", limit: 50))
{
Console.WriteLine($"Processing page with {page.Items.Count} repositories");
// Process entire page at once
foreach (var repo in page.Items)
{
// ...
}
}
Example 3: With Cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
await foreach (var item in api.EnumerateItemsAsync(cancellationToken: cts.Token))
{
// Process item
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation timed out");
}
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Cursor:
| Package | Downloads |
|---|---|
|
Akiles.ApiClient
Unofficial .NET API Client for Akiles. |
|
|
Cursor.EntityFrameworkCore
Entity Framework Core integration for cursor-based pagination. Provides extension methods to convert IQueryable queries into cursor-paginated results with support for single and compound keys. |
|
|
Onomondo.ApiClient
Unofficial .NET API Client for Onomondo. |
GitHub repositories
This package is not used by any popular GitHub repositories.