Skip to content

Commit

Permalink
fixed wrong calculated pagination size. (#628)
Browse files Browse the repository at this point in the history
* fixed wrong calculated pagination size.
  • Loading branch information
b4rtaz authored Aug 30, 2022
1 parent b1027d2 commit a6b30fc
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 66 deletions.
17 changes: 17 additions & 0 deletions .changeset/three-apes-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@moralisweb3/evm-api': patch
'@moralisweb3/evm-utils': patch
---

Fixed a wrong calculated pagination size. Added the `hasNext()` method to a paginated result. Now you must call it before you call the `next()` method.

```ts
let response = await Moralis.EvmApi.token.getNFTOwners({
/* ... */
});

while (response.hasNext()) {
response = await response.next();
// ...
}
```
47 changes: 32 additions & 15 deletions packages/apiUtils/src/resolvers/ApiPaginatedResultAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,59 @@ import { ApiErrorCode, MoralisApiError } from '@moralisweb3/core';
import { ApiResultAdapter } from './ApiResultAdapter';
import { PaginatedResult } from './PaginatedEndpoint';

/**
* The adapter for a paginated API result.
*/
export class ApiPaginatedResultAdapter<
Data extends PaginatedResult<unknown>,
AdaptedData,
JSONData,
Params,
> extends ApiResultAdapter<Data, AdaptedData, JSONData, Params> {
private _nextCall?: () => Promise<ApiPaginatedResultAdapter<Data, AdaptedData, JSONData, Params>>;

constructor(
public constructor(
data: Data,
adapter: (data: Data, params: Params) => AdaptedData,
jsonAdapter: (data: AdaptedData) => JSONData,
params: Params,
nextCall?: () => Promise<ApiPaginatedResultAdapter<Data, AdaptedData, JSONData, Params>>,
private readonly nextHandler?: () => Promise<ApiPaginatedResultAdapter<Data, AdaptedData, JSONData, Params>>,
) {
super(data, adapter, jsonAdapter, params);
this._nextCall = nextCall;
}

next = () => {
if (!this._nextCall) {
/**
* Checks an existence of the next page.
*
* @returns `true` if a next page exists, otherwise `false`.
*/
public hasNext = () => {
return !!this.nextHandler;
};

/**
* Gets a next page of the paginated result.
*
* @returns a new instance of a paginated adapter.
*/
public next = () => {
if (!this.nextHandler) {
throw new MoralisApiError({
code: ApiErrorCode.PAGE_LIMIT_EXCEEDED,
message: 'Cannot call .next(). Page limit exceeded.',
message:
'Page limit exceeded! Before call this method check an existence of the next page by .hasNext() method.',
});
}

return this._nextCall();
return this.nextHandler();
};

get pagination() {
/**
* @returns an info about pagination.
*/
public get pagination() {
return {
total: this._data.total,
page: this._data.page,
pageSize: this._data.page_size,
cursor: this._data.cursor,
total: this.data.total,
page: this.data.page,
pageSize: this.data.page_size,
cursor: this.data.cursor,
};
}
}
69 changes: 42 additions & 27 deletions packages/apiUtils/src/resolvers/ApiResultAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,57 @@ export type JSONApiResult<Value extends object = object> =
}
| JSONApiResult[];

/**
* The adapter for the API result.
*/
export class ApiResultAdapter<Data, AdaptedData, JSONData, Params> {
protected _data: Data;
protected _adapter: (data: Data, params: Params) => AdaptedData;
protected _jsonAdapter: (data: AdaptedData) => JSONData;
protected _params: Params;
public constructor(
protected readonly data: Data,
protected readonly adapter: (data: Data, params: Params) => AdaptedData,
protected readonly jsonAdapter: (data: AdaptedData) => JSONData,
protected readonly params: Params,
) {}

constructor(
data: Data,
adapter: (data: Data, params: Params) => AdaptedData,
jsonAdapter: (data: AdaptedData) => JSONData,
params: Params,
) {
this._data = data;
this._adapter = adapter;
this._jsonAdapter = jsonAdapter;
this._params = params;
/**
* @returns a raw data from the API.
*/
public get raw() {
return this.data;
}

get raw() {
return this._data;
/**
* @returns the result adapted into SDK types.
*/
public get result(): AdaptedData {
return this.adapter(this.data, this.params);
}

get result(): AdaptedData {
return this._adapter(this._data, this._params);
/**
* @returns the result in the JSON format.
*/
public toJSON(): JSONData {
return this.jsonAdapter(this.result);
}

// TODO: Cast all to primitive types
toJSON() {
return this._jsonAdapter(this.result);
}
/**
* @returns the result in the raw format.
*/
public format(formatType: ApiFormatType.RAW): Data;

/**
* @returns athe result in the JSON format.
*/
public format(formatType: ApiFormatType.JSON): unknown;

/**
* @returns the result adapted into SDK types.
*/
public format(formatType: ApiFormatType.NORMAL): AdaptedData;

format(formatType: ApiFormatType.RAW): Data;
// WIP: add type
format(formatType: ApiFormatType.JSON): unknown;
format(formatType: ApiFormatType.NORMAL): AdaptedData;
format(formatType: ApiFormatType) {
/**
* Format the result to a specific format.
*/
public format(formatType: ApiFormatType) {
if (formatType === ApiFormatType.RAW) {
return this.raw;
}
Expand Down
10 changes: 6 additions & 4 deletions packages/apiUtils/src/resolvers/PaginatedEndpointResolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { checkObjEqual } from '../utils/checkObjEqual';
import { getNextParams } from '../utils/getNextParams';
import { tryGetNextPageParams } from '../utils/tryGetNextPageParams';
import { ApiPaginatedResultAdapter } from './ApiPaginatedResultAdapter';
import { ApiConfig } from '../config/ApiConfig';
import { MoralisCore, ApiErrorCode, Config, MoralisApiError, RequestController } from '@moralisweb3/core';
Expand Down Expand Up @@ -85,8 +84,11 @@ export class PaginatedEndpointResolver<
};

private resolveNextCall = (params: Params, result: Awaited<PaginatedResult<ApiResult>>) => {
const nextParams = getNextParams(params, result);
return checkObjEqual(params, nextParams) ? undefined : () => this.fetch(nextParams);
const nextParams = tryGetNextPageParams(params, result);
if (nextParams) {
return () => this.fetch(nextParams);
}
return undefined;
};

private createUrl(params: Params): string {
Expand Down
3 changes: 0 additions & 3 deletions packages/apiUtils/src/utils/checkObjEqual.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/apiUtils/src/utils/getNextParams.ts

This file was deleted.

3 changes: 1 addition & 2 deletions packages/apiUtils/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './getNextParams';
export * from './checkObjEqual';
export * from './tryGetNextPageParams';
61 changes: 61 additions & 0 deletions packages/apiUtils/src/utils/tryGetNextPageParams.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { PaginatedParams, PaginatedResult } from '../resolvers/PaginatedEndpoint';
import { tryGetNextPageParams } from './tryGetNextPageParams';

describe('tryGetNextPageParams', () => {
describe('cursor mode', () => {
function createData(total: number, page: number, pageSize: number): [PaginatedParams, PaginatedResult<null>] {
const currentParams = {
cursor: '0x1',
};
const result = {
total,
page,
page_size: pageSize,
cursor: '0x2',
result: null,
};
return [currentParams, result];
}

it('returns params when next page exists', () => {
const [currentParams, result] = createData(950, 9, 100);
const nextParams = tryGetNextPageParams(currentParams, result);
expect(nextParams?.cursor).toEqual('0x2');
});

it('returns null when next page does not exist', () => {
const [currentParams, result] = createData(950, 10, 100);
const nextParams = tryGetNextPageParams(currentParams, result);
expect(nextParams).toBeNull();
});
});

describe('offset mode', () => {
function createData(total: number, page: number, pageSize: number): [PaginatedParams, PaginatedResult<null>] {
const currentParams = {
limit: 100,
offset: 900,
};
const result = {
total,
page,
page_size: pageSize,
cursor: '',
result: null,
};
return [currentParams, result];
}

it('returns params when next page exists', () => {
const [currentParams, result] = createData(950, 9, 100);
const nextParams = tryGetNextPageParams(currentParams, result);
expect(nextParams?.offset).toEqual(1000);
});

it('returns null when next page does not exist', () => {
const [currentParams, result] = createData(950, 10, 100);
const nextParams = tryGetNextPageParams(currentParams, result);
expect(nextParams).toBeNull();
});
});
});
19 changes: 19 additions & 0 deletions packages/apiUtils/src/utils/tryGetNextPageParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PaginatedParams, PaginatedResult } from '../resolvers/PaginatedEndpoint';

export const tryGetNextPageParams = <Params extends PaginatedParams>(
currentParams: Params,
result: PaginatedResult<unknown>,
) => {
const hasNextPage = result.total > result.page_size * result.page;
if (!hasNextPage) {
return null;
}

const nextParams = { ...currentParams };
if (result.cursor) {
nextParams.cursor = result.cursor;
} else {
nextParams.offset = (result.page + 1) * (nextParams.limit || 500);
}
return nextParams;
};

0 comments on commit a6b30fc

Please sign in to comment.