Skip to content

fix: Parse.Query.findAll returns empty array calling withCount#2854

Open
dplewis wants to merge 6 commits intoparse-community:alphafrom
dplewis:findall-withcount
Open

fix: Parse.Query.findAll returns empty array calling withCount#2854
dplewis wants to merge 6 commits intoparse-community:alphafrom
dplewis:findall-withcount

Conversation

@dplewis
Copy link
Member

@dplewis dplewis commented Jan 7, 2026

Pull Request

Issue

Closes: #2621

Approach

  • Allow findAll to work properly with withCount
  • Improve typings
  • Fix flaky test

Summary by CodeRabbit

  • New Features

    • Query methods find() and findAll() can return total counts alongside results when enabled.
    • Option to retrieve raw JSON results for queries.
    • Ability to enable/disable query explain plans.
  • Tests

    • Added/expanded tests covering JSON result retrieval, count-aware findAll, and explain(false) behavior.

@parse-github-assistant
Copy link

parse-github-assistant bot commented Jan 7, 2026

🚀 Thanks for opening this pull request!

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Change query APIs so find() and findAll() may return either a plain array or an object { results, count }; implementation and typings updated to unwrap wrapped responses during batch pagination. Tests added for count, raw JSON (json: true), and explain(false) behavior.

Changes

Cohort / File(s) Summary
ParseQuery method return type & logic
src/ParseQuery.ts, types/ParseQuery.d.ts
Changed find() and findAll() signatures to `Promise<T[]
Tests — query behaviors
integration/test/ParseQueryTest.js, src/__tests__/ParseQuery-test.js
Added/updated tests to assert find/findAll raw JSON (json: true) behavior, findAll with count returns { results, count }, and that explain(false) disables explain plans.
Type expansions for tests
types/tests.ts
Expanded FieldType union to include boolean, Date, Parse.File, Parse.GeoPoint, any[], and object; updated test type expectations for find() and findAll() unions; removed eslint wrapper comments.
Minor typing addition
src/EventuallyQueue.ts
Added explicit any type annotation for results in byHash method.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • mtrezza
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: enabling Parse.Query.findAll to work correctly when withCount is used, which directly addresses the core issue.
Description check ✅ Passed The description includes the issue link (#2621), approach, and tasks checkboxes as required by the template. However, the approach section is brief and non-descriptive.
Linked Issues check ✅ Passed The PR addresses the core requirement from issue #2621 by modifying find and findAll return types to support withCount, implementing count handling logic, and adding tests to verify the count functionality works correctly.
Out of Scope Changes check ✅ Passed All changes are in scope: type system updates (ParseQuery.ts, types/), test additions (ParseQueryTest.js, tests/), and EventuallyQueue typing adjustment. These directly support the withCount fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Jan 7, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@codecov
Copy link

codecov bot commented Jan 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (89fdb07) to head (d2c1bc9).
⚠️ Report is 1 commits behind head on alpha.

Additional details and impacted files
@@            Coverage Diff            @@
##             alpha     #2854   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           64        64           
  Lines         6235      6240    +5     
  Branches      1477      1495   +18     
=========================================
+ Hits          6235      6240    +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/EventuallyQueue.ts (1)

345-353: Prefer unwrapping find’s union result instead of any

query.find now returns T[] | { results: T[]; count: number }, but results is typed as any and used as results[0]. That works today (no withCount / json here) but hides type issues if this ever changes.

You can keep this future‑proof and typed without any:

Suggested unwrapping
-      const results: any = await query.find({ sessionToken });
-      if (results.length > 0) {
-        return EventuallyQueue.sendQueueCallback(results[0], queueObject);
-      }
+      const response = await query.find({ sessionToken });
+      const results = Array.isArray(response) ? response : response.results || [];
+      if (results.length > 0) {
+        return EventuallyQueue.sendQueueCallback(results[0], queueObject);
+      }
types/ParseQuery.d.ts (1)

228-231: Updated find / findAll typings correctly model count-wrapped results

The new union return types for find and findAll align with the runtime behavior when withCount is used and with the implementation in src/ParseQuery.ts. This is a reasonable breaking typing change and matches the tests’ expectations.

If you want to reduce duplication later, you could alias the wrapped shape (e.g., type FindWithCountResult<T> = { results: T[]; count: number }) and reuse it here, but that’s purely cosmetic.

Also applies to: 247-250

types/tests.ts (1)

907-932: Minor mismatch between FieldType and notGeopoint Exclude target

FieldType now includes Parse.GeoPoint (not Parse.GeoPoint[]), but notGeopoint is defined as Exclude<FieldType, Parse.GeoPoint[]>. This still makes the $ExpectError call behave as intended, but the type is misleading.

You can tighten this to reflect the actual union:

Optional cleanup
-  notGeopoint: Exclude<FieldType, Parse.GeoPoint[]>,
+  notGeopoint: Exclude<FieldType, Parse.GeoPoint>,
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf2ef13 and 84c8526.

📒 Files selected for processing (5)
  • integration/test/ParseQueryTest.js
  • src/EventuallyQueue.ts
  • src/ParseQuery.ts
  • types/ParseQuery.d.ts
  • types/tests.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/EventuallyQueue.ts (1)
src/ParseRelation.ts (1)
  • query (120-141)
integration/test/ParseQueryTest.js (2)
src/ParseRelation.ts (1)
  • query (120-141)
src/ParseQuery.ts (1)
  • count (765-780)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (Node 24, 24.1.0)
  • GitHub Check: build (Node 20, 20.19.0)
  • GitHub Check: build (Node 22, 22.12.0)
🔇 Additional comments (7)
types/tests.ts (1)

2048-2053: Type test expectations for find / findAll match new union return

The updated $ExpectType annotations for queryUntyped.find(), queryUntyped.findAll(), and queryTyped.find() correctly capture the new T[] | { results: T[]; count: number } return type shape and keep the typings in sync with ParseQuery.d.ts and the implementation.

Also applies to: 2063-2064

src/ParseQuery.ts (3)

678-721: find union return type is consistent with runtime behavior

The updated find signature (Promise<T[] | { results: T[]; count: number }>), plus the conditional { results, count } wrapping when response.count is present, matches how the method already behaves (including offline queries via _handleOfflineQuery). No issues spotted here.


739-748: findAll correctly returns { results, count } when withCount is enabled

The new findAll implementation:

  • Uses eachBatch to load all pages.
  • Aggregates all objects into a single results array.
  • When _count is true (from withCount(true)), returns { results, count: results.length }; otherwise returns results directly.

This satisfies the desired behavior for findAll+withCount without coupling to server-side count, and preserves existing json behavior.


936-944: Unwrapping { results, count } in eachBatch fixes the pagination bug

The new unwrapping:

const [response] = await Promise.all([query.find(findOptions), ...]);
let results: any = response;
if (results.results) {
  results = results.results;
}

ensures that results is always an array before .length is used for pagination. This removes the previous failure mode where withCount(true) caused results to be an object and results.length was undefined, short-circuiting findAll with an empty result.

integration/test/ParseQueryTest.js (3)

55-63: Raw JSON tests now exercise both find and findAll

Using let results = await query.find({ json: true }); followed by results = await query.findAll({ json: true }); and asserting on results[0] validates that both methods return raw JSON when json: true and withCount is not used. This aligns with the implementation and helps guard the json-path behavior after the union-type changes.


110-127: New findAll + withCount test directly covers the original bug

it('can do findAll query with count', ...) exercises:

  • Creating multiple matching objects.
  • Calling query.withCount(true);
  • Using const { results, count } = await query.findAll();

and asserts both results.length and count are 4. This is exactly the scenario that was previously returning an empty array due to the pagination bug, so this test is a solid regression guard.


2419-2429: explain(false) test confirms explicit disable of explain mode

The it('can query with explain false', ...) case verifies that calling query.explain(false); restores normal find() behavior (array of 3 objects, not an explain plan). This matches the explain(explain = true) implementation and ensures toggling the flag works as intended.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 7, 2026
@dplewis dplewis requested a review from a team January 8, 2026 03:40
@dplewis
Copy link
Member Author

dplewis commented Jan 27, 2026

@mtrezza Is this good to merge?

@mtrezza
Copy link
Member

mtrezza commented Jan 28, 2026

@coderabbitai, is such a change in return type replicable to Parse SDKs with strongly typed languages like Swift? To have a method return multiple types looks problematic. Do we have any examples of doing that in other places in this repo for public APIs of Parse.Query?

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for other methods in ParseQuery that return union types or have conditional return types
echo "=== Searching for union return types in ParseQuery.ts ==="
rg -n -A3 -B3 'Promise<.*\|.*>' src/ParseQuery.ts

echo -e "\n=== Searching for other count-related methods in ParseQuery ==="
rg -n -A5 -B3 'count.*Promise' src/ParseQuery.ts

echo -e "\n=== Checking the type definitions for other methods that might return different types ==="
cat types/ParseQuery.d.ts | grep -A2 'count\|Count'

echo -e "\n=== Looking for any existing patterns of conditional returns in ParseQuery public methods ==="
ast-grep --pattern 'async $METHOD($$$): Promise<$$$> {
  $$$
  if ($$$) {
    return { $$$ }
  }
  return $$$
}'

Length of output: 2922


🏁 Script executed:

#!/bin/bash
# Check how the current count mechanism works and if there's documentation
echo "=== Looking at withCount implementation ==="
rg -n -A10 'withCount' src/ParseQuery.ts

echo -e "\n=== Check if there are any other APIs that conditionally change return shape ==="
rg -n -B3 -A5 'results.*count|count.*results' src/ParseQuery.ts

echo -e "\n=== Look at how first() handles undefined to see if there's a pattern for optional returns ==="
ast-grep --pattern 'first($$$) {
  $$$
}'

echo -e "\n=== Check Parse Server documentation or comments about count behavior ==="
rg -n -C5 'includeCount|withCount' src/ParseQuery.ts types/ParseQuery.d.ts

Length of output: 5360


🏁 Script executed:

#!/bin/bash
# Check if there are any other examples of conditional return shapes in the codebase
echo "=== Looking for other methods that return objects with 'results' property ==="
rg -n -B5 -A5 'return.*\{.*results.*\}' src/ParseQuery.ts | head -50

echo -e "\n=== Check if there's a separate count() method that returns just the count ==="
ast-grep --pattern 'count($$$): Promise<number> {
  $$$
}'

echo -e "\n=== Search for any GitHub issues or comments about withCount and strongly-typed languages ==="
rg -n 'Swift|Android|Kotlin|strongly.typed|strong.typed' --type md

echo -e "\n=== Look for any existing TypeScript type guards or utility types in the codebase ==="
fd -e ts -e d.ts | xargs rg -l 'type guard|isArray|hasOwnProperty.*results' | head -10

Length of output: 1237


🏁 Script executed:

#!/bin/bash
# Let's check how iOS SDK and Android SDK handle this - search for any references or documentation
echo "=== Check if there are any references to other SDK implementations ==="
fd README.md CONTRIBUTING.md | xargs rg -i 'ios|swift|android|kotlin|sdk' -C2 | head -40

echo -e "\n=== Look at the count() method implementation to understand the separate API ==="
ast-grep --pattern 'count($OPTS): Promise<number> {
  $$$
}' 

echo -e "\n=== Check for any migration guides or breaking changes documentation ==="
fd -e md | xargs rg -l 'breaking|migration|CHANGELOG' | head -5

echo -e "\n=== Look at similar patterns in first() which returns T | undefined ==="
rg -n -B3 -A10 'first\(options' src/ParseQuery.ts

Length of output: 2853


🌐 Web query:

Parse iOS SDK Swift withCount findAll implementation

💡 Result:

In Parse (including the iOS SDKs), a “withCount” style query is implemented by sending the standard Parse query with the REST parameter count=1 (optionally alongside a limit). The server then returns:

  • results: the objects for that page
  • count: the total number of objects matching the query criteria (ignores pagination) (docs.parseplatform.org)

In the ObjC-based Parse iOS SDK (PFQuery), you can either:

  • fetch objects (find...) and separately count (countObjects...), or
  • use REST directly if you need both “page of results + total count” in one round trip. (docs.parseplatform.org)

Typical findAll implementation (client-side)

A “findAll” convenience is usually implemented as repeated paged queries until no more results are returned:

  1. Choose a page size (limit). (Parse iOS SDK defaults to 100 if you don’t set it.) (docs.parseplatform.org)
  2. Loop:
    • request limit results with skip = pageIndex * limit (or equivalent pagination if your SDK supports it)
    • append to an array
    • stop when returned count < limit

Pseudo-Swift:

var all: [PFObject] = []
let pageSize = 100
var skip = 0

while true {
  let q = PFQuery(className: "GameScore")
  q.limit = pageSize
  q.skip = skip
  let page = try await q.findObjects()
  all += page
  if page.count < pageSize { break }
  skip += pageSize
}

That’s effectively what a findAll helper does; withCount is just the Parse REST count=1 flag layered into the same query request. (docs.parseplatform.org)

Citations:


@mtrezza Great question! After analyzing the codebase, I found some important information:

No Precedent for Conditional Object Return Shapes

There are NO existing examples in Parse.Query's public API that return different object shapes conditionally like this PR does. Here's what I found:

  1. first() returns Promise<T | undefined> - but this is a nullable type of the same shape, not completely different return structures
  2. count() returns Promise<number> - a separate, dedicated method
  3. No other methods return array | { results, count } union types

Current Implementation Pattern

The find() method currently returns either:

  • T[] when withCount is false
  • { results: T[], count: number } when withCount is true

This creates a discriminated union that requires runtime type checking.

Implications for Strongly-Typed SDKs

Based on the Parse iOS SDK documentation, Swift handles this differently:

  • Separate methods: findObjects() for results vs countObjects() for counts
  • No combined findAll + withCount pattern
  • If both needed, developers make two separate calls or use REST directly

In strongly-typed languages like Swift, this union return type would likely require:

  • Option 1: Enum/Result wrapper type (e.g., QueryResult<T> with cases for .objects([T]) and .objectsWithCount([T], Int))
  • Option 2: Separate methods (e.g., findAll() vs findAllWithCount())
  • Option 3: Always return the wrapped format { results, count } even when count isn't requested

Concerns

  1. Type Safety: Callers must check result shape at runtime
  2. Breaking Change: Existing code expecting T[] will break if withCount behavior changes
  3. Cross-SDK Consistency: Other Parse SDKs don't appear to use this pattern
  4. TypeScript Narrowing: Users need type guards to safely access the result

Alternative Approaches to Consider

Would it make sense to either:

  1. Have findAll() ignore withCount and always return T[] (since it fetches ALL objects anyway, the count would equal results.length)
  2. Create a separate findAllWithCount() method
  3. Always return { results: T[], count?: number } for consistency

What's your preference for maintaining cross-SDK compatibility?

@mtrezza
Copy link
Member

mtrezza commented Jan 28, 2026

This goes back to the old issue in the parse-server repo (can't find it right now) that it's a conceptual mistake to have an array as a response object. Parse Server does this at the moment and it's responsible for a bunch of limitations. Generally, we are moving towards type safety, so a type ambiguity as suggested here is contrary to that. If an API is only implementable in 1 SDK because it doesn't enforce types, while not being able to add it also to other strongly typed SDKs in the future, then there's no point in adding it as it proofs that it's conceptually wrong. People can always use the REST API, and it's also a somewhat superfluous option if count is equal the array length. Maybe we should deprecate the withCount option in parse-server.

@dplewis
Copy link
Member Author

dplewis commented Jan 29, 2026

it's also a somewhat superfluous option if count is equal the array length

This is true for findAll() or if find() returns all the objects. withCount is useful for pagination since you can show the total number of objects without having to make 2 calls everytime.

Callers must check result shape at runtime

When a dev use withCount it's documented that the resulting shape will change. This is technically two separate calls withCount(true), withCount(false).

@mtrezza
Copy link
Member

mtrezza commented Jan 29, 2026

.withCont additionally returns the total number of objects that match the query, regardless of skip and limit params?

@dplewis
Copy link
Member Author

dplewis commented Jan 29, 2026

Yes, there are tests cases for it.

@mtrezza
Copy link
Member

mtrezza commented Jan 29, 2026

Then this makes sense; but the issue is still, if we can't add it to other SDKs because of type issues, then there's no point in adding it only to the JS SDK. Maybe we need a new API for this?

@dplewis
Copy link
Member Author

dplewis commented Jan 29, 2026

Maybe we need a new API for this?

The other SDK's can do what the Swift SDK does for this. Instead of withCount setting _count param there withCount returns the objects and count itself.

@mtrezza
Copy link
Member

mtrezza commented Jan 30, 2026

So withCount there is a distinct method, like await Parse.Query.withCount() instead of await Parse.Query.findAll()?

@dplewis
Copy link
Member Author

dplewis commented Jan 30, 2026

Yeah

@mtrezza
Copy link
Member

mtrezza commented Jan 30, 2026

For JS SDK that would be

const query = new Parse.Query(TestObject);
const results = await query.withCount();

instead of

const query = new Parse.Query(TestObject);
query.withCount(true);
const results = await query.findAll();

?

@dplewis
Copy link
Member Author

dplewis commented Jan 30, 2026

Yeah but that would be a breaking change

@mtrezza
Copy link
Member

mtrezza commented Jan 30, 2026

That would be fine as we avoid the type ambiguity. A breaking change should be acceptable, since it's unrelated to Parse Server and easy to roll out. We're really correcting this type ambiguity that has been introduced before.

@dplewis
Copy link
Member Author

dplewis commented Jan 30, 2026

Cool, I’m going to close this

@dplewis dplewis closed this Jan 30, 2026
@mtrezza
Copy link
Member

mtrezza commented Jan 31, 2026

Yes, I've summarized the discussion in a comment on the issue.

@dplewis
Copy link
Member Author

dplewis commented Jan 31, 2026

@mtrezza You got the PR?

@mtrezza
Copy link
Member

mtrezza commented Jan 31, 2026

What PR?

@dplewis dplewis reopened this Feb 7, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/ParseQuery.ts`:
- Line 678: The change to ParseQuery.find from Promise<T[]> to Promise<T[] | {
results: T[]; count: number }> introduces a breaking discriminated union; revert
the find signature back to find(options?: QueryOptions): Promise<T[]> and
implement a new method (e.g., findWithCount(options?: QueryOptions): Promise<{
results: T[]; count: number }>) that returns the results + count wrapper,
updating the ParseQuery class methods accordingly (adjust implementations
currently in find to be reused by findWithCount) and update any
callers/tests/types to use the new findWithCount when they need the count.
- Around line 739-748: The current findAll in ParseQuery returns a fabricated
count (results.length) instead of the server-provided total; update findAll to
capture and return the server count supplied by the first batch response rather
than computing results.length. Specifically, change the eachBatch usage so the
batch callback (from eachBatch) returns or exposes server metadata (e.g.,
totalCount/ count from the server) and in findAll capture that value on the
first invocation (store in a local variable like serverCount) and, when
this._count is true, return { results, count: serverCount } instead of count:
results.length; adjust eachBatch (and its callback signature) to provide the
server count if it currently only yields objects.
- Around line 936-943: The cloned query used by eachBatch (created via
fromJSON(this.className, this.toJSON())) is preserving the internal _count flag
and causing every query.find call to send count=1; strip or delete the `_count`
property from the cloned query before paginating so paged requests don't force a
total-count compute. Also make the response unwrapping in the Promise.all result
more robust by detecting whether the response is an array (e.g., use
Array.isArray(response)) instead of relying on the presence of
`results.results`; update the logic around `query.find`, `response`, and
`results` to unwrap only when the response is not an Array.
🧹 Nitpick comments (1)
src/ParseQuery.ts (1)

740-742: Accumulating results with spread in a loop is O(n²).

results = [...results, ...objects] on every batch copies the entire accumulated array each time. For large datasets (which is the whole point of findAll), this becomes expensive. Use push with spread or Array.prototype.concat and reassign.

Proposed fix
-    let results: T[] = [];
     await this.eachBatch((objects: T[]) => {
-      results = [...results, ...objects];
+      results.push(...objects);
     }, options);

* the query completes.
*/
find(options?: QueryOptions): Promise<T[]> {
find(options?: QueryOptions): Promise<T[] | { results: T[]; count: number }> {
Copy link

@coderabbitai coderabbitai bot Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Union return type is a breaking change for TypeScript consumers.

Changing find() from Promise<T[]> to Promise<T[] | { results: T[]; count: number }> forces every caller to narrow the type before accessing array methods. This was already flagged in the PR discussion by @mtrezza as conceptually problematic and inconsistent across SDKs. Consider a separate method (e.g., findWithCount()) or always returning a wrapper to avoid a discriminated union.

🤖 Prompt for AI Agents
In `@src/ParseQuery.ts` at line 678, The change to ParseQuery.find from
Promise<T[]> to Promise<T[] | { results: T[]; count: number }> introduces a
breaking discriminated union; revert the find signature back to find(options?:
QueryOptions): Promise<T[]> and implement a new method (e.g.,
findWithCount(options?: QueryOptions): Promise<{ results: T[]; count: number }>)
that returns the results + count wrapper, updating the ParseQuery class methods
accordingly (adjust implementations currently in find to be reused by
findWithCount) and update any callers/tests/types to use the new findWithCount
when they need the count.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could actually introduce a method findWithCount() for this, to not mess up the types while providing some relieve for developers - what do you think @dplewis?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +739 to 748
async findAll(options?: BatchOptions): Promise<T[] | { results: T[], count: number}> {
let results: T[] = [];
await this.eachBatch((objects: T[]) => {
result = [...result, ...objects];
results = [...results, ...objects];
}, options);
return result;
if (this._count) {
return { results, count: results.length };
}
return results;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

findAll with withCount returns a fabricated count instead of the server-provided total.

count: results.length (line 745) is the length of the locally accumulated array, not the server's authoritative count. While these should converge after all pages are fetched, it diverges from the semantics of find().withCount() where the count is server-computed (and can differ from results.length if, e.g., ACLs filter rows at query time vs. cursor time).

If withCount support is kept in findAll, consider capturing the server count from the first batch response instead.

🤖 Prompt for AI Agents
In `@src/ParseQuery.ts` around lines 739 - 748, The current findAll in ParseQuery
returns a fabricated count (results.length) instead of the server-provided
total; update findAll to capture and return the server count supplied by the
first batch response rather than computing results.length. Specifically, change
the eachBatch usage so the batch callback (from eachBatch) returns or exposes
server metadata (e.g., totalCount/ count from the server) and in findAll capture
that value on the first invocation (store in a local variable like serverCount)
and, when this._count is true, return { results, count: serverCount } instead of
count: results.length; adjust eachBatch (and its callback signature) to provide
the server count if it currently only yields objects.

Comment on lines +936 to +943
const [response] = await Promise.all([
query.find(findOptions),
Promise.resolve(previousResults.length > 0 && callback(previousResults)),
]);
let results: any = response;
if (results.results) {
results = results.results;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unnecessary count=1 sent on every batch request & fragile unwrapping.

Two concerns here:

  1. Performance: eachBatch clones the query via fromJSON(this.className, this.toJSON()) (line 924), which preserves _count. Every paginated batch request sends count=1 to the server, forcing it to compute the total count on each page — completely wasted work. Strip _count from the cloned query used for pagination.

  2. Fragile duck-typing: results.results (line 941) relies on the assumption that a plain T[] never has a .results property. This works today but is brittle. A more explicit check (e.g., !Array.isArray(response)) would be safer.

Proposed fix
     const query = ParseQuery.fromJSON(this.className, this.toJSON());
+    query._count = false;
     query.ascending('objectId');
     query._limit = options.batchSize || 100;

And for the unwrapping:

-        let results: any = response;
-        if (results.results) {
-          results = results.results;
-        }
+        const results: T[] = Array.isArray(response)
+          ? response
+          : (response as { results: T[] }).results;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [response] = await Promise.all([
query.find(findOptions),
Promise.resolve(previousResults.length > 0 && callback(previousResults)),
]);
let results: any = response;
if (results.results) {
results = results.results;
}
const [response] = await Promise.all([
query.find(findOptions),
Promise.resolve(previousResults.length > 0 && callback(previousResults)),
]);
const results: T[] = Array.isArray(response)
? response
: (response as { results: T[] }).results;
🤖 Prompt for AI Agents
In `@src/ParseQuery.ts` around lines 936 - 943, The cloned query used by eachBatch
(created via fromJSON(this.className, this.toJSON())) is preserving the internal
_count flag and causing every query.find call to send count=1; strip or delete
the `_count` property from the cloned query before paginating so paged requests
don't force a total-count compute. Also make the response unwrapping in the
Promise.all result more robust by detecting whether the response is an array
(e.g., use Array.isArray(response)) instead of relying on the presence of
`results.results`; update the logic around `query.find`, `response`, and
`results` to unwrap only when the response is not an Array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

After calling withCount on Parse.Query, findAll returns empty array regardless of result

3 participants