Skip to main content

Overview

q/limit caps how many Entities one query returns. To read a dataset larger than your page size, paginate with a cursor on fibery/id.

Pagination pattern

  1. Order results with q/order-by: [[["fibery/id"], "q/asc"]].
  2. Set q/limit to your page size + 1. The extra Entity is a sentinel — its presence in the response means another page exists.
  3. On every page after the first, filter with q/where: [">", ["fibery/id"], "$last-seen-id"] and pass the previous page’s last fibery/id in params.
  4. Stop when a response contains pageSize Entities or fewer.
If your query already has a q/where, combine it with the cursor filter using ["q/and", <existing>, [">", ["fibery/id"], "$last-seen-id"]].

Example: page through a database

The first page has no cursor — order by fibery/id and request one more than the page size:
const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    command: 'fibery.entity/query',
    args: {
      query: {
        'q/from': 'Cricket/Player',
        'q/select': ['fibery/id', 'Cricket/Name'],
        'q/order-by': [[['fibery/id'], 'q/asc']],
        'q/limit': 1001
      }
    }
  })
});
const data = await response.json();
If the response contains more than 1000 Entities, drop the last one, take the fibery/id of the 1000th, and request the next page using it as a cursor:
const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    command: 'fibery.entity/query',
    args: {
      query: {
        'q/from': 'Cricket/Player',
        'q/select': ['fibery/id', 'Cricket/Name'],
        'q/where': ['>', ['fibery/id'], '$last-seen-id'],
        'q/order-by': [[['fibery/id'], 'q/asc']],
        'q/limit': 1001
      },
      params: { '$last-seen-id': '21e578b0-9752-11e9-81b9-4363f716f666' }
    }
  })
});
const data = await response.json();
A reusable helper that wraps the loop:
JavaScript
const PAGE_SIZE = 1000;
const QUERY_LIMIT = PAGE_SIZE + 1;

async function queryEntitiesPaginated({ query, params }) {
  const allEntities = [];
  let lastSeenId = null;
  let hasMorePages = true;

  while (hasMorePages) {
    const paginatedQuery = {
      ...query,
      'q/order-by': [[['fibery/id'], 'q/asc']],
      'q/limit': QUERY_LIMIT,
    };

    if (lastSeenId) {
      const existingWhere = query['q/where'];
      paginatedQuery['q/where'] = existingWhere
        ? ['q/and', existingWhere, ['>', ['fibery/id'], '$last-seen-id']]
        : ['>', ['fibery/id'], '$last-seen-id'];
    }

    const paginatedParams = lastSeenId
      ? { ...params, '$last-seen-id': lastSeenId }
      : params;

    const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
      method: 'POST',
      headers: {
        'Authorization': 'Token YOUR_TOKEN',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        command: 'fibery.entity/query',
        args: { query: paginatedQuery, params: paginatedParams },
      }),
    });
    const data = await response.json();
    const entities = data.result;

    if (entities.length > PAGE_SIZE) {
      const pageEntities = entities.slice(0, PAGE_SIZE);
      allEntities.push(...pageEntities);
      lastSeenId = pageEntities[pageEntities.length - 1]['fibery/id'];
    } else {
      allEntities.push(...entities);
      hasMorePages = false;
    }
  }

  return allEntities;
}

Loading collections and nested graphs

Collection sub-queries can’t be paginated directly. In every sub-query, set q/limit: 100 and treat any collection that comes back at exactly the limit as potentially truncated. To get the full collection for those parents, query the child Database directly with the cursor pattern, scoped to one parent at a time. Step 1 — page through parents, selecting each parent’s collection with q/limit: 100:
const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    command: 'fibery.entity/query',
    args: {
      query: {
        'q/from': 'Cricket/Team',
        'q/select': [
          'fibery/id',
          'Cricket/Name',
          { 'Cricket/Current Players': {
              'q/select': ['fibery/id', 'Cricket/Name'],
              'q/limit': 100
            }
          }
        ],
        'q/order-by': [[['fibery/id'], 'q/asc']],
        'q/limit': 1001
      }
    }
  })
});
const data = await response.json();
Step 2 — for any parent whose collection came back at 100 items, paginate the child Database directly. Filter by the back-reference Field that points from the child to the parent (here, Cricket/Current Team on Cricket/Player), combined with the fibery/id cursor:
const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    command: 'fibery.entity/query',
    args: {
      query: {
        'q/from': 'Cricket/Player',
        'q/select': ['fibery/id', 'Cricket/Name'],
        'q/where': [
          'q/and',
          ['=', ['Cricket/Current Team', 'fibery/id'], '$team-id'],
          ['>', ['fibery/id'], '$last-seen-id']
        ],
        'q/order-by': [[['fibery/id'], 'q/asc']],
        'q/limit': 1001
      },
      params: {
        '$team-id': '21e578b0-9752-11e9-81b9-4363f716f666',
        '$last-seen-id': '216c2a00-9752-11e9-81b9-4363f716f666'
      }
    }
  })
});
const data = await response.json();
The same approach extends to deeper graphs: page through the top-level Entities, then for each one page through its children, then for each child page through its grandchildren, and so on.
This pattern assumes the child Database has a single Field pointing back to the parent (a one-to-many relation). For many-to-many collections, filter the child Database by the inverse collection Field with q/in, passing parent ids as a list — for example "q/where": ["q/in", ["Cricket/Former Players", "fibery/id"], "$player-ids"]. Combine with the fibery/id cursor under q/and as in Step 2.

Avoid “q/no-limit”

"q/no-limit" returns every matching Entity in a single response. On large Databases the request will time out. Use bounded limits and the pagination pattern above.
Top-level queries. "q/no-limit" is supported but discouraged. Use q/limit: 1000 and the cursor pattern. Collection sub-queries. "q/no-limit" is allowed today but planned for deprecation. Use q/limit: 100 in sub-queries; if you need the full collection, paginate the child Database separately as shown in Loading collections and nested graphs.