Savvi Studio

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:

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...of for 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

Next steps: See Streams Documentation for transformation patterns.