Cursor Usage Patterns
Purpose: Common patterns for using cursors
Last Updated: 2024-11-26
Overview
This guide covers common usage patterns for cursors implementing the abstract cursor interface.
Source Code Reference
For complete cursor interface and types, see:
src/lib/cursor/abstract-cursor.ts- Base cursor implementationsrc/lib/cursor/types.ts- Cursor interfacessrc/lib/db/api/QueryCursor.ts- PostgreSQL implementation
Basic Iteration
AsyncIterable Protocol
Cursors implement AsyncIterable<T>, enabling for await...of loops:
const cursor = new SomeCursor(config);
for await (const item of cursor) {
console.log(item);
}
Manual Fetching
For more control over batching:
const cursor = new SomeCursor(config);
while (await cursor.hasMore()) {
const batch = await cursor.fetch(50);
await processBatch(batch);
}
Stream Transformation
Convert cursor to stream for transformations:
const results = await cursor
.stream()
.map(item => transform(item))
.filter(item => item.isValid)
.collect();
See Streams Documentation for transformation patterns.
Token Management
Save and Resume
// Save cursor position
const cursor = new SomeCursor(config);
await cursor.fetch(50);
const token = cursor.getToken();
localStorage.setItem('cursor', token);
// Resume from saved position
const savedToken = localStorage.getItem('cursor');
const resumedCursor = new SomeCursor({
...config,
initialToken: savedToken
});
Check for More Data
const cursor = new SomeCursor(config);
if (await cursor.hasMore()) {
const nextBatch = await cursor.fetch();
}
Error Handling
try {
const cursor = new SomeCursor(config);
for await (const item of cursor) {
await processItem(item);
}
} catch (error) {
if (error.code === 'INVALID_CURSOR') {
// Handle invalid cursor token
console.error('Cursor expired or invalid');
} else {
// Handle other errors
throw error;
}
}
Real-World Examples
Database Query Pagination
See src/lib/db/api/QueryCursor.ts for the production implementation.
API Pagination
const cursor = new ApiCursor(client, '/api/users');
// Fetch all users in batches
const allUsers = [];
for await (const user of cursor) {
allUsers.push(user);
}
Infinite Scroll
function useInfiniteCursor<T>(cursor: Cursor<T>) {
const [items, setItems] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const loadMore = async () => {
if (loading || !(await cursor.hasMore())) return;
setLoading(true);
try {
const batch = await cursor.fetch(20);
setItems(prev => [...prev, ...batch]);
} finally {
setLoading(false);
}
};
return { items, loadMore, hasMore: () => cursor.hasMore() };
}
Best Practices
✅ Do
- Use
for await...offor simple iteration - Save cursor tokens for resumption
- Check
hasMore()before fetching - Handle errors appropriately
- Use streams for transformations
❌ Don't
- Mix cursor iteration with manual fetching
- Ignore cursor state
- Fetch without checking
hasMore() - Ignore error handling
- Duplicate transformation logic
Related Documentation
- Implementation Guide - Creating custom cursors
- Best Practices - Guidelines and anti-patterns
- Streams - Data transformation patterns
- Database Cursors - PostgreSQL implementation
Next steps: See Streams Documentation for transformation patterns.