Skip to content

feat[getAll]: ENG-11765 add fetchTotalCount param from Content API as an option for getAll in SDKs#4352

Merged
AishwaryaParab merged 8 commits intomainfrom
ENG-11765_add_fetchTotalCount_to_getAll
Mar 26, 2026
Merged

feat[getAll]: ENG-11765 add fetchTotalCount param from Content API as an option for getAll in SDKs#4352
AishwaryaParab merged 8 commits intomainfrom
ENG-11765_add_fetchTotalCount_to_getAll

Conversation

@AishwaryaParab
Copy link
Copy Markdown
Contributor

@AishwaryaParab AishwaryaParab commented Mar 11, 2026

Description

The Content API (/api/v3/content/:model) already supports a fetchTotalCount query parameter. When fetchTotalCount=true is passed, the server:

  1. Runs a countDocuments query in parallel with the data fetch
  2. Returns { results: BuilderContent[], totalCount: number } instead of the usual { results: BuilderContent[] }

This is already used internally by admin UI components (e.g. ComponentsUsageTableSymbolEntriesTable) which call the API directly. However, the public SDK getAll method does not expose this parameter and currently always returns only BuilderContent[], silently discarding totalCount from the response.

This PR:

  1. Accepts a new fetchTotalCount option in GetContentOptions
  2. Forwards the parameter to the Content API as a query string param
  3. When fetchTotalCount: true, returns { results: BuilderContent[], totalCount: number } instead of BuilderContent[]
  4. When fetchTotalCount is absent or false, preserves the existing return shape BuilderContent[] for full backwards compatibility

Screenshot
https://www.loom.com/share/72c25dbbd7fd4293ae5e3c794d4a8822


Note

Medium Risk
Adds an optional new response shape for getAll() and threads a new query param through request building, which could affect caching/keying and downstream typing/handling when enabled. Default behavior remains unchanged, keeping backward-compat risk limited to users opting in.

Overview
getAll() now accepts a new fetchTotalCount option, forwards it to the Content API as fetchTotalCount=true, and (when enabled) resolves to { results, totalCount } instead of a bare BuilderContent[].

Core request handling stores totalCount from API responses per request key, and getAll() updates its keying/SSR behavior to reliably retrieve the count. Tests were added to cover URL param inclusion and the conditional return shape, and a changeset bumps @builder.io/sdk and @builder.io/react minor versions.

Written by Cursor Bugbot for commit 73b1d7d. This will update automatically on new commits. Configure here.

Comment thread packages/core/src/builder.class.ts
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Mar 11, 2026

View your CI Pipeline Execution ↗ for commit 73b1d7d

Command Status Duration Result
nx test @e2e/hydrogen ✅ Succeeded 5m 17s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-26 05:32:58 UTC

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 23, 2026

🦋 Changeset detected

Latest commit: 73b1d7d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@builder.io/sdk Minor
@builder.io/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/core/src/builder.class.ts Outdated
Comment thread packages/sdks/src/functions/get-content/types.ts Outdated
Comment thread packages/core/src/builder.class.ts Outdated
@AishwaryaParab AishwaryaParab requested review from a team, midhunadarvin and sanyamkamat and removed request for a team March 23, 2026 07:23
Comment thread packages/core/src/builder.class.ts Outdated
Comment on lines +3052 to +3060
getAll(
modelName: string,
options?: GetContentOptions & {
req?: IncomingMessage;
res?: ServerResponse;
apiKey?: string;
authToken?: string;
}
): Promise<BuilderContent[]>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this function overload required as it's similar to the below one ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The below one is the implementation of the getAll() function. In total, we have two function overloads. The first one kicks in when fetchTotalCount is passed and totalCount is received. The second one is when fetchTotalCount is not passed and we don't get totalCount back.

The implementation signature is never visible to the callers. So, if we call builder.getAll('blog-post', {limit: 5}) [without the second overload], TS throws an error saying no matching signature found.

Comment thread packages/core/src/builder.class.ts Outdated
res?: ServerResponse;
apiKey?: string;
authToken?: string;
fetchTotalCount: true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this needed here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fetchTotalCount is passed as true even after defining it in GetContentOptions because here, we're explicitly saying that the value of fetchTotalCount will be true. If in case it's false or if it's not passed at all, we will directly return BuilderContent[] instead.

Alternatively, a cleaner way of doing this can be:

getAll<T extends boolean = false>(
  modelName: string,
  options?: GetContentOptions & {
    req?: IncomingMessage;
    res?: ServerResponse;
    apiKey?: string;
    authToken?: string;
    fetchTotalCount?: T;
  }
): Promise<T extends true ? { results: BuilderContent[]; totalCount: number } : BuilderContent[]>

rather than using 2 overloads. Do you think this is better?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Go with cleaner approach 😄

Comment thread packages/core/src/builder.class.ts Outdated
})
.promise();
.promise()
.then(results => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this required when we are already passing fetchTotalCount in request params?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

observer.next(testModifiedResults) (https://github.com/BuilderIO/builder/blob/566a33238fb147a30b9390ea6fc8709a6939902a/packages/core/src/builder.class.ts#L2879C50-L2880C)
This only emits BuilderContent[] and the totalCount returned by result.totalCount is discarded at this point. That is why totalCountByKey is used as a cache to store totalCount during this flush.

* When `true`, the API will also return the total number of matching entries
* regardless of `limit`/`offset`. Useful for building numbered pagination.
*/
fetchTotalCount?: boolean;
Copy link
Copy Markdown
Contributor

@sanyamkamat sanyamkamat Mar 23, 2026

Choose a reason for hiding this comment

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

why are we adding to gen2 when we do not target it in this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removing this from here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not removed yet.

Copy link
Copy Markdown
Contributor

@sanyamkamat sanyamkamat left a comment

Choose a reason for hiding this comment

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

LGTM

Comment thread packages/core/src/builder.class.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

return {
results,
totalCount: key !== undefined ? instance.totalCountByKey[key] ?? 0 : 0,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale totalCount returned when key resolves to cached observable

Medium Severity

When queueGetContent finds an existing observable for the same key (line ~2484 in the currentObservable check), it replays the cached value via nextTick without making an API call. In that case, totalCountByKey is never populated (or holds a stale value from a previous call), but the .then() in getAll still reads from instance.totalCountByKey[key], returning either 0 or an outdated totalCount. This means repeated getAll calls with fetchTotalCount: true on the browser (where instance === this and observables persist) can return a stale or zero totalCount when the cache short-circuits the fetch.

Additional Locations (1)
Fix in Cursor Fix in Web

@AishwaryaParab AishwaryaParab merged commit 560a5d0 into main Mar 26, 2026
93 of 94 checks passed
@AishwaryaParab AishwaryaParab deleted the ENG-11765_add_fetchTotalCount_to_getAll branch March 26, 2026 05:33
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.

3 participants