Skip to content

Client query options (sort, limit, projection, upsert) are silently dropped — Realm Web SDK wire-format mismatch #82

Description

@ameliekassnernovacode

Summary

Options passed to client-side data-source calls — sort, limit, projection,
upsert — are silently ignored. A collection.find(filter, { sort, limit })
runs as an unbounded, unsorted find({}) and returns the entire collection
in natural order. Only the filter/query is honored. The same flattening
affects findOne, count, and the findOneAnd* family.

This is a wire-format mismatch between the official realm-web SDK and the
flowerbase data-source controller. It is present on current main.

Impact

  • find — intended "newest 100" feed query instead streams every document.
    For a large collection (70k+ docs with embedded sub-objects, in our case) this
    is effectively a hang on the client, and the relevant index is never used
    because no sort/limit reaches the cursor.
  • findOne(…, { sort }) — the common "get the latest/earliest" pattern
    returns an arbitrary natural-order document rather than the sorted one. This is
    a correctness bug, not just performance.
  • count(…, { limit }) — a capped count returns the full count.
  • findOneAndUpdate(…, { upsert: true })upsert is dropped, so an
    intended upsert silently no-ops when no document matches.

Root cause

The realm-web SDK flattens the options object before sending: it lifts
the option fields up to the top level of the service-call argument (and, for
find/findOne, renames projectionproject). There is no options
object on the wire.

// realm-web: RemoteMongoDBCollection.find
find(filter = {}, options = {}) {
  return this.functions.find({
    database, collection,
    query:   filter,
    project: options.projection,  // top-level, renamed "project"
    sort:    options.sort,        // top-level
    limit:   options.limit,       // top-level
  });
}

Resulting HTTP body for find:

{ "name": "find", "service": "mongodb-atlas",
  "arguments": [ { "database": "...", "collection": "...",
                   "query": {}, "project": null, "sort": {"date":-1}, "limit": 100 } ] }

The data-source controller
(packages/flowerbase/src/features/functions/controller.ts) destructures only a
nested options object (and reads projection, not project):

const [{ database, collection, key, query, filter, update,
         projection, options, returnNewDocument, document,
         documents, operations, pipeline = [] }] = args;

Because the SDK never sends options, the controller passes options: undefined
into executeQuery. In executeQuery's find branch
(packages/flowerbase/src/features/functions/utils.ts), the
parsedOptions?.sort / parsedOptions?.skip / parsedOptions?.limit guards are
therefore all skipped, and the cursor is built and .toArray()-ed with no sort,
projection, or limit. The cursor plumbing itself is correct — the values are lost
one layer up, in the controller's destructuring, because they arrive at the top
level instead of inside options.

Affected methods

Every read/mutate method flattens its options the same way, so the controller
drops them across the family:

SDK method Sent at top level Honored Dropped
find query, project, sort, limit query sort, limit, project
findOne query, project, sort query sort, project
count query, limit query limit
findOneAndUpdate filter, update, sort, projection, upsert, returnNewDocument filter, update, returnNewDocument¹ sort, projection, upsert
findOneAndReplace filter, update, sort, projection, upsert, returnNewDocument filter, update, returnNewDocument¹ sort, projection, upsert
findOneAndDelete filter, sort, projection filter sort, projection

¹ returnNewDocument survives only because executeQuery special-cases it
(synthesizing { returnDocument: … } when options is absent). No other option
gets that treatment.

Note the SDK renames projectionproject for find/findOne, but keeps
projection for the findOneAnd* mutators, so both spellings appear on the wire.

Steps to reproduce

// any collection with more than `limit` documents
const docs = await collection.find({}, { sort: { someDate: -1 }, limit: 10 });
// Expected: 10 documents, newest first
// Actual:   every document in the collection, natural order

Environment

  • @flowerforce/flowerbase 1.10.0 (confirmed identical on current main)
  • realm-web client SDK

Possibly related

#54 (large concurrent find() memory/perf) — different cause, but unbounded
find results would compound it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions