-
Notifications
You must be signed in to change notification settings - Fork 100
feat: add dedicated methods for cursor based pagination [CAPI-2357] #2824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 11 commits
cd8d735
263f23a
de2e56c
84d693a
790f711
a2a540e
09e8e07
0603508
725fc0d
4810416
5fc9db5
fa0dca8
49dbd1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,6 +59,7 @@ | |
| - [Configuration](#configuration) | ||
| - [Reference Documentation](#reference-documentation) | ||
| - [Contentful Javascript resources](#contentful-javascript-resources) | ||
| - [Cursor Based Pagination](#cursor-based-pagination) | ||
| - [REST API reference](#rest-api-reference) | ||
| - [Versioning](#versioning) | ||
| - [Reach out to us](#reach-out-to-us) | ||
|
|
@@ -226,6 +227,17 @@ The benefits of using the "plain" version of the client, over the legacy version | |
| - The ability to scope CMA client instance to a specific `spaceId`, `environmentId`, and `organizationId` when initializing the client. | ||
| - You can pass a concrete values to `defaults` and omit specifying these params in actual CMA methods calls. | ||
|
|
||
| ## Cursor Based Pagination | ||
|
|
||
| Cursor-based pagination is supported on collection endpoints for content types, entries, and assets. To use cursor-based pagination, use the related entity methods `getAssetsWithCursor`, `getContentTypesWithCursor`, and `getEntriesWithCursor` | ||
|
|
||
| ```js | ||
| const response = await environment.getEntriesWithCursor({ limit: 10 }); | ||
| console.log(response.items); // Array of items | ||
| console.log(response.pages?.next); // Cursor for next page | ||
| ``` | ||
| Use the value from `response.pages.next` to fetch the next page. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also add an example on how to use the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can do |
||
|
|
||
| ## Legacy Client Interface | ||
|
|
||
| The following code snippet is an example of the legacy client interface, which reads and writes data as a sequence of nested requests: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,7 +247,7 @@ | |
| select?: string | ||
| links_to_entry?: string | ||
|
|
||
| [key: string]: any | ||
| } | ||
|
|
||
| export interface SpaceQueryOptions extends PaginationQueryOptions { | ||
|
|
@@ -359,6 +359,7 @@ | |
| } | ||
|
|
||
| export interface BasicCursorPaginationOptions extends Omit<BasicQueryOptions, 'skip'> { | ||
| skip?: never | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this skip unnecessary given the Omit above?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this type used in all the non cursor based functions as signature? 🤔
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ethan-ozelius-contentful It's there because unless strictly forbidden, the value can still be passed in at runtime. I wanted to make sure to have a type error there just in case. @marcolink Could you point to where that is the case? I'm not sure I follow right now, apologies
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here for example: (not changed in this PR)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. I'm not sure why, but can investigate. I was using the same type to idiomatically match how we were using it elsewhere.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, changing that now would likely create a breaking change ...
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, we can make a point to investigate what's happening here and if need be spin up another ticket? |
||
| pageNext?: string | ||
| pagePrev?: string | ||
| } | ||
|
|
@@ -487,6 +488,7 @@ | |
| ): MRReturn<'AppInstallation', 'getForOrganization'> | ||
|
|
||
| (opts: MROpts<'Asset', 'getMany', UA>): MRReturn<'Asset', 'getMany'> | ||
| (opts: MROpts<'Asset', 'getManyWithCursor', UA>): MRReturn<'Asset', 'getManyWithCursor'> | ||
| (opts: MROpts<'Asset', 'getPublished', UA>): MRReturn<'Asset', 'getPublished'> | ||
| (opts: MROpts<'Asset', 'get', UA>): MRReturn<'Asset', 'get'> | ||
| (opts: MROpts<'Asset', 'update', UA>): MRReturn<'Asset', 'update'> | ||
|
|
@@ -567,6 +569,9 @@ | |
|
|
||
| (opts: MROpts<'ContentType', 'get', UA>): MRReturn<'ContentType', 'get'> | ||
| (opts: MROpts<'ContentType', 'getMany', UA>): MRReturn<'ContentType', 'getMany'> | ||
| ( | ||
| opts: MROpts<'ContentType', 'getManyWithCursor', UA>, | ||
| ): MRReturn<'ContentType', 'getManyWithCursor'> | ||
| (opts: MROpts<'ContentType', 'update', UA>): MRReturn<'ContentType', 'update'> | ||
| (opts: MROpts<'ContentType', 'create', UA>): MRReturn<'ContentType', 'create'> | ||
| (opts: MROpts<'ContentType', 'createWithId', UA>): MRReturn<'ContentType', 'createWithId'> | ||
|
|
@@ -616,6 +621,7 @@ | |
| ): MRReturn<'EnvironmentTemplateInstallation', 'getForEnvironment'> | ||
|
|
||
| (opts: MROpts<'Entry', 'getMany', UA>): MRReturn<'Entry', 'getMany'> | ||
| (opts: MROpts<'Entry', 'getManyWithCursor', UA>): MRReturn<'Entry', 'getManyWithCursor'> | ||
| (opts: MROpts<'Entry', 'getPublished', UA>): MRReturn<'Entry', 'getPublished'> | ||
| (opts: MROpts<'Entry', 'get', UA>): MRReturn<'Entry', 'get'> | ||
| (opts: MROpts<'Entry', 'patch', UA>): MRReturn<'Entry', 'patch'> | ||
|
|
@@ -1234,6 +1240,11 @@ | |
| headers?: RawAxiosRequestHeaders | ||
| return: CollectionProp<AssetProps> | ||
| } | ||
| getManyWithCursor: { | ||
| params: GetSpaceEnvironmentParams & CursorBasedParams & { releaseId?: string } | ||
| headers?: RawAxiosRequestHeaders | ||
| return: CursorPaginatedCollectionProp<AssetProps> | ||
| } | ||
| get: { | ||
| params: GetSpaceEnvironmentParams & { assetId: string; releaseId?: string } & QueryParams | ||
| headers?: RawAxiosRequestHeaders | ||
|
|
@@ -1482,6 +1493,10 @@ | |
| params: GetSpaceEnvironmentParams & QueryParams | ||
| return: CollectionProp<ContentTypeProps> | ||
| } | ||
| getManyWithCursor: { | ||
| params: GetSpaceEnvironmentParams & CursorBasedParams | ||
| return: CursorPaginatedCollectionProp<ContentTypeProps> | ||
| } | ||
| create: { | ||
| params: GetSpaceEnvironmentParams | ||
| payload: CreateContentTypeProps | ||
|
|
@@ -1650,6 +1665,10 @@ | |
| params: GetSpaceEnvironmentParams & QueryParams & { releaseId?: string } | ||
| return: CollectionProp<EntryProps<any>> | ||
| } | ||
| getManyWithCursor: { | ||
| params: GetSpaceEnvironmentParams & CursorBasedParams & { releaseId?: string } | ||
| return: CursorPaginatedCollectionProp<EntryProps<any>> | ||
| } | ||
| get: { | ||
| params: GetSpaceEnvironmentParams & { entryId: string; releaseId?: string } & QueryParams | ||
| return: EntryProps<any> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,10 @@ | |
| CursorBasedParams, | ||
| QueryOptions, | ||
| } from './common-types' | ||
| import { | ||
| normalizeCursorPaginationParameters, | ||
| normalizeCursorPaginationResponse, | ||
| } from './common-utils' | ||
| import type { BasicQueryOptions, MakeRequest } from './common-types' | ||
| import entities from './entities' | ||
| import type { CreateAppInstallationProps } from './entities/app-installation' | ||
|
|
@@ -15,11 +19,11 @@ | |
| CreateAppActionCallProps, | ||
| AppActionCallRawResponseProps, | ||
| } from './entities/app-action-call' | ||
| import type { | ||
| AssetFileProp, | ||
| AssetProps, | ||
| CreateAssetFromFilesOptions, | ||
| CreateAssetProps, | ||
| import { | ||
| type AssetFileProp, | ||
| type AssetProps, | ||
| type CreateAssetFromFilesOptions, | ||
| type CreateAssetProps, | ||
| } from './entities/asset' | ||
| import type { CreateAssetKeyProps } from './entities/asset-key' | ||
| import type { | ||
|
|
@@ -40,12 +44,12 @@ | |
| } from './entities/release' | ||
| import { wrapRelease, wrapReleaseCollection } from './entities/release' | ||
|
|
||
| import type { ContentTypeProps, CreateContentTypeProps } from './entities/content-type' | ||
| import type { | ||
| CreateEntryProps, | ||
| EntryProps, | ||
| EntryReferenceOptionsProps, | ||
| EntryReferenceProps, | ||
| import { type ContentTypeProps, type CreateContentTypeProps } from './entities/content-type' | ||
| import { | ||
| type CreateEntryProps, | ||
| type EntryProps, | ||
| type EntryReferenceOptionsProps, | ||
| type EntryReferenceProps, | ||
| } from './entities/entry' | ||
| import type { EnvironmentProps } from './entities/environment' | ||
| import type { CreateExtensionProps } from './entities/extension' | ||
|
|
@@ -75,9 +79,10 @@ | |
| */ | ||
| export default function createEnvironmentApi(makeRequest: MakeRequest) { | ||
| const { wrapEnvironment } = entities.environment | ||
| const { wrapContentType, wrapContentTypeCollection } = entities.contentType | ||
| const { wrapEntry, wrapEntryCollection } = entities.entry | ||
| const { wrapAsset, wrapAssetCollection } = entities.asset | ||
| const { wrapContentType, wrapContentTypeCollection, wrapContentTypeCursorPaginatedCollection } = | ||
| entities.contentType | ||
| const { wrapEntry, wrapEntryCollection, wrapEntryTypeCursorPaginatedCollection } = entities.entry | ||
| const { wrapAsset, wrapAssetCollection, wrapAssetTypeCursorPaginatedCollection } = entities.asset | ||
| const { wrapAssetKey } = entities.assetKey | ||
| const { wrapLocale, wrapLocaleCollection } = entities.locale | ||
| const { wrapSnapshotCollection } = entities.snapshot | ||
|
|
@@ -238,7 +243,7 @@ | |
| * .then((bulkAction) => console.log(bulkAction)) | ||
| * ``` | ||
| */ | ||
| getBulkAction<T extends BulkActionPayload = any>(bulkActionId: string): Promise<BulkAction<T>> { | ||
| const raw: EnvironmentProps = this.toPlainObject() | ||
|
|
||
| return makeRequest({ | ||
|
|
@@ -492,6 +497,44 @@ | |
| }, | ||
| }).then((data) => wrapContentTypeCollection(makeRequest, data)) | ||
| }, | ||
|
|
||
| /** | ||
| * Gets a collection of Content Types with cursor based pagination | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two URLs listed in this @param should probably point to the follow URL right? |
||
| * @param query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details. | ||
| * @return Promise for a collection of Content Types | ||
| * @example ```javascript | ||
| * const contentful = require('contentful-management') | ||
| * | ||
| * const client = contentful.createClient({ | ||
| * accessToken: '<content_management_api_key>' | ||
| * }) | ||
| * | ||
| * client.getSpace('<space_id>') | ||
| * .then((space) => space.getEnvironment('<environment-id>')) | ||
| * .then((environment) => environment.getContentTypesWithCursor()) | ||
| * .then((response) => console.log(response.items)) | ||
| * .catch(console.error) | ||
| * ``` | ||
| */ | ||
| getContentTypesWithCursor(query: BasicCursorPaginationOptions = {}) { | ||
| const raw = this.toPlainObject() as EnvironmentProps | ||
| const normalizedQueryParams = normalizeCursorPaginationParameters(query) | ||
| return makeRequest({ | ||
| entityType: 'ContentType', | ||
| action: 'getMany', | ||
| params: { | ||
| spaceId: raw.sys.space.sys.id, | ||
| environmentId: raw.sys.id, | ||
| query: createRequestConfig({ query: normalizedQueryParams }).params, | ||
| }, | ||
| }).then((data) => | ||
| wrapContentTypeCursorPaginatedCollection( | ||
| makeRequest, | ||
| normalizeCursorPaginationResponse(data), | ||
| ), | ||
| ) | ||
| }, | ||
|
|
||
| /** | ||
| * Creates a Content Type | ||
| * @param data - Object representation of the Content Type to be created | ||
|
|
@@ -740,6 +783,45 @@ | |
| }).then((data) => wrapEntryCollection(makeRequest, data)) | ||
| }, | ||
|
|
||
| /** | ||
| * Gets a collection of Entries with cursor based pagination | ||
| * Warning: if you are using the select operator, when saving, any field that was not selected will be removed | ||
| * from your entry in the backend | ||
| * @param query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details. | ||
| * @return Promise for a collection of Entries | ||
| * @example ```javascript | ||
| * const contentful = require('contentful-management') | ||
| * | ||
| * const client = contentful.createClient({ | ||
| * accessToken: '<content_management_api_key>' | ||
| * }) | ||
| * | ||
| * client.getSpace('<space_id>') | ||
| * .then((space) => space.getEnvironment('<environment-id>')) | ||
| * .then((environment) => environment.getEntriesWithCursor({'content_type': 'foo'})) // you can add more queries as 'key': 'value' | ||
| * .then((response) => console.log(response.items)) | ||
| * .catch(console.error) | ||
| * ``` | ||
| */ | ||
| getEntriesWithCursor(query: BasicCursorPaginationOptions = {}) { | ||
| const raw = this.toPlainObject() as EnvironmentProps | ||
| const normalizedQueryParams = normalizeCursorPaginationParameters(query) | ||
| return makeRequest({ | ||
| entityType: 'Entry', | ||
| action: 'getMany', | ||
| params: { | ||
| spaceId: raw.sys.space.sys.id, | ||
| environmentId: raw.sys.id, | ||
| query: createRequestConfig({ query: normalizedQueryParams }).params, | ||
| }, | ||
| }).then((data) => | ||
| wrapEntryTypeCursorPaginatedCollection( | ||
| makeRequest, | ||
| normalizeCursorPaginationResponse(data), | ||
| ), | ||
| ) | ||
| }, | ||
|
|
||
| /** | ||
| * Gets a collection of published Entries | ||
| * @param query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details. | ||
|
|
@@ -955,6 +1037,46 @@ | |
| }, | ||
| }).then((data) => wrapAssetCollection(makeRequest, data)) | ||
| }, | ||
|
|
||
| /** | ||
| * Gets a collection of Assets with cursor based pagination | ||
| * Warning: if you are using the select operator, when saving, any field that was not selected will be removed | ||
| * from your entry in the backend | ||
| * @param query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details. | ||
| * @return Promise for a collection of Assets | ||
| * @example ```javascript | ||
| * const contentful = require('contentful-management') | ||
| * | ||
| * const client = contentful.createClient({ | ||
| * accessToken: '<content_management_api_key>' | ||
| * }) | ||
| * | ||
| * client.getSpace('<space_id>') | ||
| * .then((space) => space.getEnvironment('<environment-id>')) | ||
| * .then((environment) => environment.getAssetsWithCursor()) | ||
| * .then((response) => console.log(response.items)) | ||
| * .catch(console.error) | ||
| * ``` | ||
| */ | ||
| getAssetsWithCursor(query: BasicCursorPaginationOptions = {}) { | ||
| const raw = this.toPlainObject() as EnvironmentProps | ||
| const normalizedQueryParams = normalizeCursorPaginationParameters(query) | ||
| return makeRequest({ | ||
| entityType: 'Asset', | ||
| action: 'getMany', | ||
| params: { | ||
| spaceId: raw.sys.space.sys.id, | ||
| environmentId: raw.sys.id, | ||
| query: createRequestConfig({ query: normalizedQueryParams }).params, | ||
| }, | ||
| }).then((data) => | ||
| wrapAssetTypeCursorPaginatedCollection( | ||
| makeRequest, | ||
| normalizeCursorPaginationResponse(data), | ||
| ), | ||
| ) | ||
| }, | ||
|
|
||
| /** | ||
| * Gets a collection of published Assets | ||
| * @param query - Object with search parameters. Check the <a href="https://www.contentful.com/developers/docs/javascript/tutorials/using-js-cda-sdk/#retrieving-entries-with-search-parameters">JS SDK tutorial</a> and the <a href="https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters">REST API reference</a> for more details. | ||
|
|
@@ -985,6 +1107,7 @@ | |
| }, | ||
| }).then((data) => wrapAssetCollection(makeRequest, data)) | ||
| }, | ||
|
|
||
| /** | ||
| * Creates a Asset. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing. | ||
| * @param data - Object representation of the Asset to be created. Note that the field object should have an upload property on asset creation, which will be removed and replaced with an url property when processing is finished. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.