NukeBase

Sorting, Limits and Pagination

A query takes five optional window parameters. They work on query() and — except for count — on querySub and querySubChanged as well.

Parameter Type Meaning
orderBy array of field names Sort by a field on each record: ["views"], ["meta","rating"]. Always an array, never a dotted string — one spelling means one canonical form everywhere (queries, indexes, subscription keys). Omit it to order by record key. Max 8 segments.
desc boolean Reverse the order. Default false.
limit integer 1–1,000,000 Maximum records to return. Implies an order — without orderBy, the window is ordered by record key.
startAt integer 0–1,000,000 Skip this many matching records. Mutually exclusive with cursor.
cursor opaque string Resume after a previous page. Pass back the nextCursor from the last response. Mutually exclusive with startAt.
count true Return only how many records match. data is null; the number is in response.count. Cannot be combined with limit, startAt or cursor, and is not supported on subscriptions.
Sorting and paging
// Top 10 posts by view count
const top = await query({
    path: ["posts"],
    query: "child.published == true",
    orderBy: ["views"],
    desc: true,
    limit: 10
});
// Read the results IN ORDER via response.order — a JSON object's key
// order is not something to depend on.
top.order.forEach(key => console.log(key, top.data[key].views));

// Order by a nested field
await query({
    path: ["products"],
    query: "true",
    orderBy: ["meta", "rating"],
    desc: true,
    limit: 20
});

// Order by record key (just omit orderBy)
await query({ path: ["messages"], query: "true", limit: 50 });

// Cursor pagination — the correct way to walk a large collection
let cursor;
for (;;) {
  const page = await query({
    path: ["events"],
    query: "true",
    orderBy: ["ts"],
    limit: 100,
    ...(cursor && { cursor })
  });
  page.order.forEach(k => handle(page.data[k]));
  if (!page.nextCursor) break;   // absent == you reached the end
  cursor = page.nextCursor;
}

// Offset pagination — simpler, but O(offset) to skip. Prefer a cursor
// for anything deep.
await query({ path: ["events"], query: "true", orderBy: ["ts"],
              startAt: 200, limit: 100 });

// Just the number
const c = await query({ path: ["users"], query: "child.active == true", count: true });
console.log(c.count);   // 8412   (c.data is null)

Records missing the orderBy field are excluded from the result, not sorted to one end. A record with no views key cannot participate in an ordering by ["views"], so orderBy: ["views"] silently narrows the result set to records that have one. If you need them included, give every record the field (even as 0 or null).

Ordering across mixed types is total and stable. Values sort by type first — null < boolean < number < string < everything else — and then by value within a type. Ties are broken by record key, where keys that look like integers sort numerically and ahead of non-integer keys. This is what makes a cursor able to resume from an exact position.

Cursors are opaque — but they are not secret. A cursor encodes the last row's sort value and key. Pass it straight back; don't parse or construct one. A malformed or over-long (> 512 character) cursor is rejected with Invalid cursor.

Query response metadata

When a query uses any window parameter (orderBy, limit, startAt, cursor or count), the response carries extra fields alongside data. A plain query({path, query}) keeps its original two-field shape and does not include them.

FieldMeaning
orderArray of matching record keys, in window order. This is the authoritative ordering — read the result through it.
planHow the query was executed: "scan", "index-filter", "index-order", "index-union" or "index-count". See Indexes.
examinedHow many records the engine had to look at. Compare it to the number returned to see whether an index is doing its job.
truncatedtrue if the engine stopped early against a hard scan limit. The accompanying status is "Incomplete" rather than "Success".
nextCursorPresent only when more records exist beyond this page. Its absence is how you detect the end.
countPresent only for count: true queries.

Hard scan limits. A query examines at most 8,000,000 records and returns at most 1,000,000. Hitting either stops the query early and returns status: "Incomplete" with truncated: true — a partial answer, clearly labelled, rather than an unbounded stall on the single-threaded engine. The query string itself is capped at 512 characters.