{"id":35324555,"url":"https://github.com/circuids/levee","last_synced_at":"2026-04-12T13:02:04.775Z","repository":{"id":325842563,"uuid":"1102191224","full_name":"Circuids/Levee","owner":"Circuids","description":"A lightweight, high-performance, dependency-free pagination engine for Flutter that brings cache-first architecture and generic page key support to your applications. Whether you're paginating REST APIs with offset/limit, Firestore with cursors, or custom pagination schemes and etc..","archived":false,"fork":false,"pushed_at":"2026-03-08T01:09:24.000Z","size":421,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-08T04:20:05.660Z","etag":null,"topics":["appwrite","firestore","flutter","flutter-apps","pagination","supabase"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Circuids.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-23T01:35:51.000Z","updated_at":"2026-03-10T06:08:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Circuids/Levee","commit_stats":null,"previous_names":["circuids/levee"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Circuids/Levee","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Circuids%2FLevee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Circuids%2FLevee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Circuids%2FLevee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Circuids%2FLevee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Circuids","download_url":"https://codeload.github.com/Circuids/Levee/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Circuids%2FLevee/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31715492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T06:22:27.080Z","status":"ssl_error","status_checked_at":"2026-04-12T06:21:52.710Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appwrite","firestore","flutter","flutter-apps","pagination","supabase"],"created_at":"2025-12-31T01:06:52.109Z","updated_at":"2026-04-12T13:02:04.760Z","avatar_url":"https://github.com/Circuids.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"src/logo.png\" alt=\"Levee Logo\" width=\"300\"/\u003e\n\n\n  [![pub package](https://img.shields.io/pub/v/levee.svg)](https://pub.dev/packages/levee)\n  [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)\n  [![Flutter](https://img.shields.io/badge/Flutter-%2302569B.svg?logo=Flutter\u0026logoColor=white)](https://flutter.dev)\n\n\u003c/div\u003e\n\n**Levee** is a lightweight, high-performance, dependency-free pagination engine for Flutter that brings **cache-first architecture** and **generic page key support** to your applications. Whether you're paginating REST APIs with offset/limit, Firestore with cursors, or custom pagination schemes, Levee provides a unified, flexible foundation.\n\n---\n\n## Table of Contents\n\n- [Features](#features-)\n- [Quick Start](#quick-start-)\n- [Cache Policies](#cache-policies-)\n- [Retry Logic](#retry-logic-)\n- [Filtering \u0026 Sorting](#filtering--sorting-)\n- [DataSource Examples](#datasource-examples-)\n- [Architecture](#architecture-️)\n- [API Reference](#api-reference-)\n- [Design Philosophy](#design-philosophy-)\n- [Contributing](#contributing-)\n- [License](#license-)\n- [Also by Circuids](#also-by-circuids)\n- [Support](#support-)\n\n---\n\n## Features \n\n- **Generic Page Keys (`K`)**: Use `int`, `String`, `DocumentSnapshot`, or custom types as page keys\n- **Dependency-Free Core**: Zero external dependencies beyond Flutter SDK\n- **Cache-First Architecture**: Four cache policies (CacheFirst, NetworkFirst, CacheOnly, NetworkOnly)\n- **Automatic Retry Logic**: Exponential backoff with configurable max attempts\n- **Advanced Filtering \u0026 Sorting**: Comprehensive `FilterQuery` system with 13+ operations\n- **Deterministic Cache Keys**: Query parameters + filters create stable cache identities\n- **Headless \u0026 UI Modes**: `LeveeBuilder` for custom UI, `LeveeCollectionView` for plug-and-play infinite scroll\n- **State Management**: Built on `ChangeNotifier` for seamless Flutter integration\n- **TTL Support**: Time-based cache expiration in `MemoryCacheStore`\n- **Type-Safe**: Full generic support with `PageData\u003cT,K\u003e` and `DataSource\u003cT,K\u003e`\n\n---\n\n## Quick Start 🚀\n\n### 1. Add to pubspec.yaml\n\n```yaml\ndependencies:\n  levee: ^1.0.0+1\n```\n\n### 2. Define Your Data Source\n\n```dart\nimport 'package:levee/levee.dart';\nimport 'package:http/http.dart' as http;\nimport 'dart:convert';\n\nclass UserDataSource implements DataSource\u003cUser, int\u003e {\n  final String baseUrl;\n\n  UserDataSource(this.baseUrl);\n\n  @override\n  Future\u003cPageData\u003cUser, int\u003e\u003e fetchPage(PageQuery\u003cint\u003e query) async {\n    // Build URL with query parameters\n    final url = Uri.parse('$baseUrl/users').replace(queryParameters: {\n      'page': query.key.toString(),\n      'limit': query.pageSize.toString(),\n      if (query.filters != null) ...buildFilterParams(query.filters!),\n    });\n\n    final response = await http.get(url);\n    final data = json.decode(response.body);\n\n    return PageData\u003cUser, int\u003e(\n      items: (data['users'] as List).map((json) =\u003e User.fromJson(json)).toList(),\n      query: query,\n      nextKey: data['hasMore'] ? query.key + 1 : null,\n      status: PageStatus.success,\n    );\n  }\n\n  Map\u003cString, String\u003e buildFilterParams(FilterQuery filters) {\n    // Convert filters to API params\n    return {\n      for (var field in filters.fields)\n        field.fieldName: field.value.toString(),\n    };\n  }\n}\n```\n\n### 3. Initialize Paginator\n\n```dart\nfinal paginator = Paginator\u003cUser, int\u003e(\n  source: UserDataSource('https://api.example.com'),\n  cache: MemoryCacheStore\u003cUser, int\u003e(),\n  pageSize: 20,\n  cachePolicy: CachePolicy.cacheFirst,\n  retryPolicy: RetryPolicy(maxAttempts: 3),\n);\n```\n\n### 4. Build Your UI\n\n**Option A: Headless with `LeveeBuilder`**\n\n```dart\nclass UserListScreen extends StatelessWidget {\n  final Paginator\u003cUser, int\u003e paginator;\n\n  UserListScreen(this.paginator);\n\n  @override\n  Widget build(BuildContext context) {\n    return LeveeBuilder\u003cUser, int\u003e(\n      paginator: paginator,\n      builder: (context, state) {\n        if (state.pages.isEmpty \u0026\u0026 state.isLoading) {\n          return Center(child: CircularProgressIndicator());\n        }\n\n        final allUsers = state.pages.expand((p) =\u003e p.items).toList();\n        \n        return ListView.builder(\n          itemCount: allUsers.length + (state.hasMore ? 1 : 0),\n          itemBuilder: (context, index) {\n            if (index == allUsers.length) {\n              paginator.loadNextPage();\n              return Center(child: CircularProgressIndicator());\n            }\n            return UserTile(user: allUsers[index]);\n          },\n        );\n      },\n    );\n  }\n}\n```\n\n**Option B: Full-Featured with `LeveeCollectionView`**\n\n```dart\nLeveeCollectionView\u003cUser, int\u003e(\n  paginator: paginator,\n  itemBuilder: (context, user) =\u003e ListTile(\n    leading: CircleAvatar(child: Text(user.name[0])),\n    title: Text(user.name),\n    subtitle: Text(user.email),\n    trailing: Icon(Icons.chevron_right),\n  ),\n  loadingBuilder: (context) =\u003e Center(\n    child: CircularProgressIndicator(),\n  ),\n  errorBuilder: (context, error) =\u003e Center(\n    child: Column(\n      mainAxisAlignment: MainAxisAlignment.center,\n      children: [\n        Icon(Icons.error_outline, size: 48, color: Colors.red),\n        SizedBox(height: 16),\n        Text('Error: $error'),\n        ElevatedButton(\n          onPressed: () =\u003e paginator.refresh(),\n          child: Text('Retry'),\n        ),\n      ],\n    ),\n  ),\n  emptyBuilder: (context) =\u003e Center(\n    child: Column(\n      mainAxisAlignment: MainAxisAlignment.center,\n      children: [\n        Icon(Icons.inbox_outlined, size: 48, color: Colors.grey),\n        SizedBox(height: 16),\n        Text('No users found'),\n      ],\n    ),\n  ),\n)\n```\n\n---\n\n## Cache Policies \n\nLevee supports four cache policies to match your data freshness requirements:\n\n| Policy | Description | Use Case |\n|--------|-------------|----------|\n| **`CacheFirst`** | Check cache first, fetch on miss | Default, balances speed and freshness |\n| **`NetworkFirst`** | Always fetch fresh, fall back to cache on error | Real-time data with offline fallback |\n| **`CacheOnly`** | Only return cached data | Offline-first, testing |\n| **`NetworkOnly`** | Always fetch fresh, ignore cache | Critical data requiring latest state |\n\n```dart\n// Example: Switch to NetworkFirst for real-time updates\npaginator.updateCachePolicy(CachePolicy.networkFirst);\n```\n\n---\n\n## Retry Logic\n\nLevee includes exponential backoff retry for transient failures:\n\n```dart\nfinal paginator = Paginator\u003cUser, int\u003e(\n  source: userDataSource,\n  retryPolicy: RetryPolicy(\n    maxAttempts: 3,\n    delay: Duration(seconds: 1),\n    maxDelay: Duration(seconds: 30),\n  ),\n);\n```\n\n**Retry Behavior:**\n- Attempts: `maxAttempts` (default: 3)\n- Delays: Exponential backoff (1s, 2s, 4s, ...)\n- Max delay: Capped at `maxDelay` (default: 30s)\n- Conditional: Use `retryIf` to retry only on specific errors\n\n---\n\n## List Mutations\n\nUpdate the paginated list instantly without refetching from the backend. Perfect for Firestore or when you already have the updated data in hand.\n\n### updateItem\n\nUpdate an existing item in the list:\n\n```dart\n// After updating Firestore\nawait postDoc.update({'likes': likes + 1});\npaginator.updateItem(\n  post.copyWith(likes: likes + 1),\n  (p) =\u003e p.id == post.id,\n);\n// UI updates instantly, no network call needed\n```\n\n### removeItem\n\nRemove an item from the list:\n\n```dart\n// After deleting from Firestore\nawait postDoc.delete();\npaginator.removeItem((post) =\u003e post.id == deletedPostId);\n// Item disappears from UI immediately\n```\n\n### insertItem\n\nInsert a new item into the list:\n\n```dart\n// After creating in Firestore\nfinal newPost = await postsCollection.add(postData);\npaginator.insertItem(\n  Post.fromFirestore(newPost),\n  position: 0, // Add to top (default)\n);\n// New item appears instantly\n```\n\n**Why use mutations?**\n- **Instant UI updates** - No waiting for network calls\n- **Save money** - Avoid expensive Firestore reads after mutations\n- **Better UX** - Immediate feedback for user actions\n- **Smart** - You already have the data after create/update/delete\n\n**Note:** These methods only update the local list. They don't sync with the backend—you should call them **after** your backend operation succeeds.\n\n---\n\n## Filtering \u0026 Sorting \n\n### Filter Operations\n\nLevee provides 13 predefined operations plus custom support:\n\n```dart\nfinal filters = FilterQuery(\n  fields: [\n    FilterField(\n      fieldName: 'status',\n      value: 'active',\n      operation: FilterOperation.equals,\n    ),\n    FilterField(\n      fieldName: 'age',\n      value: 18,\n      operation: FilterOperation.greaterThan,\n    ),\n    FilterField(\n      fieldName: 'tags',\n      value: 'flutter',\n      operation: FilterOperation.arrayContains,\n    ),\n  ],\n  sorts: [\n    SortField(fieldName: 'createdAt', descending: true),\n  ],\n);\n\nfinal query = PageQuery\u003cint\u003e(\n  key: 1,\n  pageSize: 20,\n  filters: filters,\n);\n```\n\n**Available Operations:**\n- `equals`, `notEquals`\n- `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`\n- `isIn`, `isNotIn`\n- `arrayContains`, `arrayContainsAny`\n- `isNull`, `isNotNull`\n- `like`\n- `custom(String code)` - For provider-specific operations\n\n### Deterministic Cache Keys\n\nFilters and sorts are part of the cache key calculation, ensuring:\n```dart\nPageQuery(key: 1, filters: FilterQuery(...)) \n// Generates different cache key than:\nPageQuery(key: 1, filters: null)\n```\n\n---\n\n## DataSource Examples \n\nLevee's `DataSource` interface is simple yet powerful—implement one method to connect any backend. Here are production-ready examples:\n\n### REST API with Offset Pagination\n\n```dart\nclass RestDataSource implements DataSource\u003cProduct, int\u003e {\n  final String baseUrl;\n  final http.Client client;\n\n  RestDataSource(this.baseUrl, this.client);\n\n  @override\n  Future\u003cPageData\u003cProduct, int\u003e\u003e fetchPage(PageQuery\u003cint\u003e query) async {\n    final offset = (query.key - 1) * query.pageSize;\n    final url = Uri.parse('$baseUrl/products').replace(queryParameters: {\n      'offset': offset.toString(),\n      'limit': query.pageSize.toString(),\n    });\n\n    final response = await client.get(url);\n    if (response.statusCode != 200) throw Exception('Failed to load products');\n\n    final data = json.decode(response.body);\n    return PageData\u003cProduct, int\u003e(\n      items: (data['products'] as List).map((j) =\u003e Product.fromJson(j)).toList(),\n      query: query,\n      nextKey: data['hasMore'] ? query.key + 1 : null,\n      status: PageStatus.success,\n    );\n  }\n}\n```\n\n### Firestore with Cursor Pagination\n\n```dart\nclass FirestoreDataSource implements DataSource\u003cPost, DocumentSnapshot?\u003e {\n  final FirebaseFirestore firestore;\n  final String collection;\n\n  FirestoreDataSource(this.firestore, this.collection);\n\n  @override\n  Future\u003cPageData\u003cPost, DocumentSnapshot?\u003e\u003e fetchPage(\n    PageQuery\u003cDocumentSnapshot?\u003e query,\n  ) async {\n    var firestoreQuery = firestore\n        .collection(collection)\n        .orderBy('createdAt', descending: true)\n        .limit(query.pageSize);\n\n    if (query.key != null) {\n      firestoreQuery = firestoreQuery.startAfterDocument(query.key!);\n    }\n\n    final snapshot = await firestoreQuery.get();\n    return PageData\u003cPost, DocumentSnapshot?\u003e(\n      items: snapshot.docs.map((doc) =\u003e Post.fromFirestore(doc)).toList(),\n      query: query,\n      nextKey: snapshot.docs.isNotEmpty ? snapshot.docs.last : null,\n      status: PageStatus.success,\n    );\n  }\n}\n```\n\n### GraphQL with Cursor Pagination\n\n```dart\nclass GraphQLDataSource implements DataSource\u003cUser, String?\u003e {\n  final GraphQLClient client;\n\n  GraphQLDataSource(this.client);\n\n  @override\n  Future\u003cPageData\u003cUser, String?\u003e\u003e fetchPage(PageQuery\u003cString?\u003e query) async {\n    final result = await client.query(QueryOptions(\n      document: gql('''\n        query GetUsers(\\$first: Int!, \\$after: String) {\n          users(first: \\$first, after: \\$after) {\n            edges { node { id name email } cursor }\n            pageInfo { hasNextPage endCursor }\n          }\n        }\n      '''),\n      variables: {'first': query.pageSize, 'after': query.key},\n    ));\n\n    if (result.hasException) throw result.exception!;\n\n    final edges = result.data!['users']['edges'] as List;\n    final pageInfo = result.data!['users']['pageInfo'];\n\n    return PageData\u003cUser, String?\u003e(\n      items: edges.map((e) =\u003e User.fromJson(e['node'])).toList(),\n      query: query,\n      nextKey: pageInfo['hasNextPage'] ? pageInfo['endCursor'] : null,\n      status: PageStatus.success,\n    );\n  }\n}\n```\n\n### Supabase with Range Pagination\n\n```dart\nclass SupabaseDataSource implements DataSource\u003cTodo, int\u003e {\n  final SupabaseClient supabase;\n  final String table;\n\n  SupabaseDataSource(this.supabase, this.table);\n\n  @override\n  Future\u003cPageData\u003cTodo, int\u003e\u003e fetchPage(PageQuery\u003cint\u003e query) async {\n    final from = query.key;\n    final to = from + query.pageSize - 1;\n\n    final response = await supabase\n        .from(table)\n        .select()\n        .range(from, to)\n        .order('created_at', ascending: false);\n\n    final todos = (response as List).map((json) =\u003e Todo.fromJson(json)).toList();\n\n    return PageData\u003cTodo, int\u003e(\n      items: todos,\n      query: query,\n      nextKey: todos.length == query.pageSize ? to + 1 : null,\n      status: PageStatus.success,\n    );\n  }\n}\n```\n\n### Local SQLite with Offset Pagination\n\n```dart\nclass SQLiteDataSource implements DataSource\u003cNote, int\u003e {\n  final Database database;\n\n  SQLiteDataSource(this.database);\n\n  @override\n  Future\u003cPageData\u003cNote, int\u003e\u003e fetchPage(PageQuery\u003cint\u003e query) async {\n    final offset = query.key;\n    final results = await database.query(\n      'notes',\n      orderBy: 'created_at DESC',\n      limit: query.pageSize,\n      offset: offset,\n    );\n\n    final notes = results.map((row) =\u003e Note.fromMap(row)).toList();\n\n    return PageData\u003cNote, int\u003e(\n      items: notes,\n      query: query,\n      nextKey: notes.length == query.pageSize ? offset + query.pageSize : null,\n      status: PageStatus.success,\n    );\n  }\n}\n```\n\n---\n\n## Architecture \n\n```\n┌─────────────────────┐\n│   UI Layer          │\n│  LeveeBuilder /     │◄──── ChangeNotifier updates\n│  CollectionView     │\n└──────────┬──────────┘\n           │\n           ▼\n┌─────────────────────┐\n│   Paginator\u003cT,K\u003e    │\n│  - Cache Policy     │\n│  - Retry Logic      │\n│  - State Management │\n└──────┬──────────┬───┘\n       │          │\n       ▼          ▼\n┌─────────────┐ ┌──────────────┐\n│ CacheStore  │ │ DataSource   │\n│  \u003cT,K\u003e      │ │   \u003cT,K\u003e      │\n└─────────────┘ └──────────────┘\n```\n\n**Key Components:**\n\n- **`Paginator\u003cT,K\u003e`**: Core engine managing cache, network, and state\n- **`DataSource\u003cT,K\u003e`**: Contract for fetching pages (implement for your backend)\n- **`CacheStore\u003cT,K\u003e`**: Contract for caching (use `MemoryCacheStore` or implement custom)\n- **`PageData\u003cT,K\u003e`**: Immutable page representation with items and metadata\n- **`PageQuery\u003cK\u003e`**: Query specification (key, size, filters, sorts)\n- **`FilterQuery`**: Declarative filtering/sorting system\n\n---\n\n## API Reference \n\n### Core Classes\n\n#### `Paginator\u003cT, K\u003e`\n\n```dart\nclass Paginator\u003cT, K\u003e extends ChangeNotifier {\n  Paginator({\n    required DataSource\u003cT, K\u003e source,\n    PageQuery\u003cK\u003e initialQuery,\n    CacheStore\u003cT, K\u003e? cache,\n    int pageSize = 20,\n    CachePolicy cachePolicy = CachePolicy.cacheFirst,\n    RetryPolicy? retryPolicy,\n    FilterQuery? initialFilter,\n  });\n\n  // State\n  PageState\u003cT\u003e get state;\n\n  // Actions\n  Future\u003cvoid\u003e loadInitial();\n  Future\u003cvoid\u003e loadNext();\n  Future\u003cvoid\u003e refresh({bool clearCache = true});\n  Future\u003cvoid\u003e updateFilter(FilterQuery? filter);\n  \n  // List Mutations\n  void updateItem(T item, bool Function(T) predicate);\n  void removeItem(bool Function(T) predicate);\n  void insertItem(T item, {int position = 0});\n  \n  void dispose();\n}\n```\n\n#### `DataSource\u003cT, K\u003e`\n\n```dart\nabstract class DataSource\u003cT, K\u003e {\n  Future\u003cPageData\u003cT, K\u003e\u003e fetchPage(PageQuery\u003cK\u003e query);\n}\n```\n\n#### `CacheStore\u003cT, K\u003e`\n\n```dart\nabstract class CacheStore\u003cT, K\u003e {\n  Future\u003cPageData\u003cT, K\u003e?\u003e get(PageQuery\u003cK\u003e query);\n  Future\u003cvoid\u003e put(PageData\u003cT, K\u003e page);\n  Future\u003cvoid\u003e remove(PageQuery\u003cK\u003e query);\n  Future\u003cvoid\u003e clear();\n}\n```\n\n### Data Structures\n\n#### `PageData\u003cT, K\u003e`\n\n```dart\nclass PageData\u003cT, K\u003e {\n  final List\u003cT\u003e items;\n  final PageQuery\u003cK\u003e query;\n  final K? nextKey;\n  final PageStatus status;\n  final Object? error;\n  final DateTime? cachedAt;\n}\n```\n\n#### `PageQuery\u003cK\u003e`\n\n```dart\nclass PageQuery\u003cK\u003e {\n  final K key;\n  final int pageSize;\n  final FilterQuery? filters;\n\n  PageQuery({\n    required this.key,\n    required this.pageSize,\n    this.filters,\n  });\n}\n```\n\n#### `FilterQuery`\n\n```dart\nclass FilterQuery {\n  final List\u003cFilterField\u003e fields;\n  final List\u003cSortField\u003e sorts;\n\n  FilterQuery({\n    required this.fields,\n    this.sorts = const [],\n  });\n}\n```\n\n---\n\n## Design Philosophy \n\n1. **Generic by Nature**: Single generic `K` for page keys supports any pagination scheme\n2. **Cache-First**: Default to fast, offline-capable experiences\n3. **Dependency-Free**: Core logic has zero external dependencies\n4. **Framework-Agnostic Core**: Contracts can be implemented in non-Flutter contexts\n5. **Deterministic Caching**: Query parameters + filters = stable cache keys\n6. **Fail-Safe**: Retry logic and cache fallbacks prevent silent failures\n7. **Developer Ergonomics**: Simple APIs with escape hatches for complexity\n\n---\n\n## Contributing \n\nContributions welcome! Fork the repo, create a feature branch, add tests, ensure `flutter test` passes, and submit a PR.\n\n---\n\n## License \n\nBSD-3-Clause License. Copyright (c) 2026 Circuids. See [LICENSE](LICENSE) for details.\n\n---\n\n## Also by Circuids\n\n| Package | Description |\n|---------|-------------|\n| [**Fairy**](https://github.com/Circuids/Fairy) | A lightweight MVVM framework for Flutter with strongly-typed reactive data binding, commands, and dependency injection — no code generation required. |\n\n---\n\n## Support \n\n- **Issues**: [GitHub Issues](https://github.com/Circuids/Levee/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/Circuids/Levee/discussions)\n\n---\n\n**Levee** - Build pagination that scales from prototypes to production.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcircuids%2Flevee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcircuids%2Flevee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcircuids%2Flevee/lists"}