Skip to content

Commit f855fd4

Browse files
authored
feat: Make all collectionClient.list methods return value also be asyncIterator of relevant data (#790)
### Description - Add `ResourceCollectionClient._getIterablePagination` which extends the return value of `ResourceCollectionClient._list` by `asyncIterator` that can be used to iterate over individual items. (It is made in a generic way and can be applied to various endpoints if desired.) - Apply `_getIterablePagination` to: - `ActorCollectionClient.list` - `ActorEnvVarCollectionClient.list` - `ActorVersionCollectionClient.list` - `BuildCollectionClient.list` - `RunCollectionClient.list` - `DatasetCollectionClient.list` - `KeyValueStoreCollectionClient.list` - `RequestQueueCollectionClient.list` - `ScheduleCollectionClient.list` - `StoreCollectionClient.list` - `TaskCollectionClient.list` - `WebhookCollectionClient.list` - `WebhookDispatchCollectionClient.list` - Add unit tests of async iteration for the single representative class `DatasetCollectionClient.list` (it works the same way on all the classes) ### Example usage It can still be used the same way as before: ``` actors = await apifyClient.store().list({ limit, offset }); // Paginated response with up to 1000 items with actor details console.log(actors.items.length); ``` Or it can be used as `asyncIterator` that can return more individual items from more than one chunk based on the `limit`, and `offset` options and also based on the number of items returned from the API: ``` for await (const actor of apifyClient.store().list({ limit, offset })) { // Single actor details console.log(actor); } ``` ### Issues - Closes: #777
1 parent 7dfc5b3 commit f855fd4

16 files changed

+402
-82
lines changed

src/base/resource_collection_client.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { PaginatedResponse, PaginationOptions } from '../utils';
12
import { parseDateFields, pluckData } from '../utils';
23
import { ApiClient } from './api_client';
34

@@ -18,6 +19,41 @@ export class ResourceCollectionClient extends ApiClient {
1819
return parseDateFields(pluckData(response.data)) as R;
1920
}
2021

22+
/**
23+
* Returns async iterator to iterate through all items and Promise that can be awaited to get first page of results.
24+
*/
25+
protected _listPaginated<T extends PaginationOptions, Data, R extends PaginatedResponse<Data>>(
26+
options: T = {} as T,
27+
): AsyncIterable<Data> & Promise<R> {
28+
const getPaginatedList = this._list.bind(this);
29+
const paginatedListPromise = getPaginatedList<T, R>(options);
30+
31+
async function* asyncGenerator() {
32+
let currentPage = await paginatedListPromise;
33+
yield* currentPage.items;
34+
const offset = options.offset ?? 0;
35+
const limit = Math.min(options.limit || currentPage.total, currentPage.total);
36+
37+
let currentOffset = offset + currentPage.items.length;
38+
let remainingItems = Math.min(currentPage.total - offset, limit) - currentPage.items.length;
39+
40+
while (
41+
currentPage.items.length > 0 && // Continue only if at least some items were returned in the last page.
42+
remainingItems > 0
43+
) {
44+
const newOptions = { ...options, limit: remainingItems, offset: currentOffset };
45+
currentPage = await getPaginatedList<T, R>(newOptions);
46+
yield* currentPage.items;
47+
currentOffset += currentPage.items.length;
48+
remainingItems -= currentPage.items.length;
49+
}
50+
}
51+
52+
return Object.defineProperty(paginatedListPromise, Symbol.asyncIterator, {
53+
value: asyncGenerator,
54+
}) as unknown as AsyncIterable<Data> & Promise<R>;
55+
}
56+
2157
protected async _create<D, R>(resource: D): Promise<R> {
2258
const response = await this.httpClient.call({
2359
url: this._url(),

src/resource_clients/actor_collection.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientSubResourceOptions } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedIterator, PaginatedList, PaginationOptions } from '../utils';
66
import type { Actor, ActorDefaultRunOptions, ActorExampleRunInput, ActorStandby } from './actor';
77
import type { ActorVersion } from './actor_version';
88

@@ -19,8 +19,21 @@ export class ActorCollectionClient extends ResourceCollectionClient {
1919

2020
/**
2121
* https://docs.apify.com/api/v2#/reference/actors/actor-collection/get-list-of-actors
22+
*
23+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
24+
* items in a single API call is limited.
25+
* ```javascript
26+
* const paginatedList = await client.list(options);
27+
*```
28+
*
29+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
30+
* retrieved.
31+
*
32+
* ```javascript
33+
* for await (const singleItem of client.list(options)) {...}
34+
* ```
2235
*/
23-
async list(options: ActorCollectionListOptions = {}): Promise<ActorCollectionListResult> {
36+
list(options: ActorCollectionListOptions = {}): PaginatedIterator<ActorCollectionListItem> {
2437
ow(
2538
options,
2639
ow.object.exactShape({
@@ -32,7 +45,7 @@ export class ActorCollectionClient extends ResourceCollectionClient {
3245
}),
3346
);
3447

35-
return this._list(options);
48+
return this._listPaginated(options);
3649
}
3750

3851
/**
@@ -50,11 +63,8 @@ export enum ActorListSortBy {
5063
LAST_RUN_STARTED_AT = 'stats.lastRunStartedAt',
5164
}
5265

53-
export interface ActorCollectionListOptions {
66+
export interface ActorCollectionListOptions extends PaginationOptions {
5467
my?: boolean;
55-
limit?: number;
56-
offset?: number;
57-
desc?: boolean;
5868
sortBy?: ActorListSortBy;
5969
}
6070

src/resource_clients/actor_env_var_collection.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientSubResourceOptions } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedList, PaginationOptions } from '../utils';
66
import type { ActorEnvironmentVariable } from './actor_version';
77

88
export class ActorEnvVarCollectionClient extends ResourceCollectionClient {
@@ -18,8 +18,23 @@ export class ActorEnvVarCollectionClient extends ResourceCollectionClient {
1818

1919
/**
2020
* https://docs.apify.com/api/v2#/reference/actors/environment-variable-collection/get-list-of-environment-variables
21+
*
22+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
23+
* items in a single API call is limited.
24+
* ```javascript
25+
* const paginatedList = await client.list(options);
26+
*```
27+
*
28+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
29+
* retrieved.
30+
*
31+
* ```javascript
32+
* for await (const singleItem of client.list(options)) {...}
33+
* ```
2134
*/
22-
async list(options: ActorEnvVarCollectionListOptions = {}): Promise<ActorEnvVarListResult> {
35+
list(
36+
options: ActorEnvVarCollectionListOptions = {},
37+
): Promise<ActorEnvVarListResult> & AsyncIterable<ActorEnvironmentVariable> {
2338
ow(
2439
options,
2540
ow.object.exactShape({
@@ -28,7 +43,7 @@ export class ActorEnvVarCollectionClient extends ResourceCollectionClient {
2843
desc: ow.optional.boolean,
2944
}),
3045
);
31-
return this._list(options);
46+
return this._listPaginated(options);
3247
}
3348

3449
/**
@@ -40,9 +55,7 @@ export class ActorEnvVarCollectionClient extends ResourceCollectionClient {
4055
}
4156
}
4257

43-
export interface ActorEnvVarCollectionListOptions {
44-
limit?: number;
45-
offset?: number;
58+
export interface ActorEnvVarCollectionListOptions extends PaginationOptions {
4659
desc?: boolean;
4760
}
4861

src/resource_clients/actor_version_collection.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientSubResourceOptions } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedList, PaginationOptions } from '../utils';
66
import type { ActorVersion, FinalActorVersion } from './actor_version';
77

88
export class ActorVersionCollectionClient extends ResourceCollectionClient {
@@ -18,8 +18,23 @@ export class ActorVersionCollectionClient extends ResourceCollectionClient {
1818

1919
/**
2020
* https://docs.apify.com/api/v2#/reference/actors/version-collection/get-list-of-versions
21+
*
22+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
23+
* items in a single API call is limited.
24+
* ```javascript
25+
* const paginatedList = await client.list(options);
26+
*```
27+
*
28+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
29+
* retrieved.
30+
*
31+
* ```javascript
32+
* for await (const singleItem of client.list(options)) {...}
33+
* ```
2134
*/
22-
async list(options: ActorVersionCollectionListOptions = {}): Promise<ActorVersionListResult> {
35+
list(
36+
options: ActorVersionCollectionListOptions = {},
37+
): Promise<ActorVersionListResult> & AsyncIterable<FinalActorVersion> {
2338
ow(
2439
options,
2540
ow.object.exactShape({
@@ -29,7 +44,7 @@ export class ActorVersionCollectionClient extends ResourceCollectionClient {
2944
}),
3045
);
3146

32-
return this._list(options);
47+
return this._listPaginated(options);
3348
}
3449

3550
/**
@@ -42,9 +57,7 @@ export class ActorVersionCollectionClient extends ResourceCollectionClient {
4257
}
4358
}
4459

45-
export interface ActorVersionCollectionListOptions {
46-
limit?: number;
47-
offset?: number;
60+
export interface ActorVersionCollectionListOptions extends PaginationOptions {
4861
desc?: boolean;
4962
}
5063

src/resource_clients/build_collection.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientOptionsWithOptionalResourcePath } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedIterator, PaginatedList, PaginationOptions } from '../utils';
66
import type { Build } from './build';
77

88
export class BuildCollectionClient extends ResourceCollectionClient {
@@ -18,8 +18,21 @@ export class BuildCollectionClient extends ResourceCollectionClient {
1818

1919
/**
2020
* https://docs.apify.com/api/v2#/reference/actors/build-collection/get-list-of-builds
21+
*
22+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
23+
* items in a single API call is limited.
24+
* ```javascript
25+
* const paginatedList = await client.list(options);
26+
*```
27+
*
28+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
29+
* retrieved.
30+
*
31+
* ```javascript
32+
* for await (const singleItem of client.list(options)) {...}
33+
* ```
2134
*/
22-
async list(options: BuildCollectionClientListOptions = {}): Promise<BuildCollectionClientListResult> {
35+
list(options: BuildCollectionClientListOptions = {}): PaginatedIterator<BuildCollectionClientListItem> {
2336
ow(
2437
options,
2538
ow.object.exactShape({
@@ -29,13 +42,11 @@ export class BuildCollectionClient extends ResourceCollectionClient {
2942
}),
3043
);
3144

32-
return this._list(options);
45+
return this._listPaginated(options);
3346
}
3447
}
3548

36-
export interface BuildCollectionClientListOptions {
37-
limit?: number;
38-
offset?: number;
49+
export interface BuildCollectionClientListOptions extends PaginationOptions {
3950
desc?: boolean;
4051
}
4152

src/resource_clients/dataset_collection.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientSubResourceOptions } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedIterator, PaginatedList, PaginationOptions } from '../utils';
66
import type { Dataset } from './dataset';
77

88
export class DatasetCollectionClient extends ResourceCollectionClient {
@@ -18,8 +18,21 @@ export class DatasetCollectionClient extends ResourceCollectionClient {
1818

1919
/**
2020
* https://docs.apify.com/api/v2#/reference/datasets/dataset-collection/get-list-of-datasets
21+
*
22+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
23+
* items in a single API call is limited.
24+
* ```javascript
25+
* const paginatedList = await client.list(options);
26+
*```
27+
*
28+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
29+
* retrieved.
30+
*
31+
* ```javascript
32+
* for await (const singleItem of client.list(options)) {...}
33+
* ```
2134
*/
22-
async list(options: DatasetCollectionClientListOptions = {}): Promise<DatasetCollectionClientListResult> {
35+
list(options: DatasetCollectionClientListOptions = {}): PaginatedIterator<Dataset> {
2336
ow(
2437
options,
2538
ow.object.exactShape({
@@ -30,7 +43,7 @@ export class DatasetCollectionClient extends ResourceCollectionClient {
3043
}),
3144
);
3245

33-
return this._list(options);
46+
return this._listPaginated(options);
3447
}
3548

3649
/**
@@ -44,10 +57,8 @@ export class DatasetCollectionClient extends ResourceCollectionClient {
4457
}
4558
}
4659

47-
export interface DatasetCollectionClientListOptions {
60+
export interface DatasetCollectionClientListOptions extends PaginationOptions {
4861
unnamed?: boolean;
49-
limit?: number;
50-
offset?: number;
5162
desc?: boolean;
5263
}
5364

src/resource_clients/key_value_store_collection.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ow from 'ow';
22

33
import type { ApiClientSubResourceOptions } from '../base/api_client';
44
import { ResourceCollectionClient } from '../base/resource_collection_client';
5-
import type { PaginatedList } from '../utils';
5+
import type { PaginatedList, PaginationOptions } from '../utils';
66
import type { KeyValueStore } from './key_value_store';
77

88
export class KeyValueStoreCollectionClient extends ResourceCollectionClient {
@@ -18,10 +18,23 @@ export class KeyValueStoreCollectionClient extends ResourceCollectionClient {
1818

1919
/**
2020
* https://docs.apify.com/api/v2#/reference/key-value-stores/store-collection/get-list-of-key-value-stores
21+
*
22+
* Awaiting the return value (as you would with a Promise) will result in a single API call. The amount of fetched
23+
* items in a single API call is limited.
24+
* ```javascript
25+
* const paginatedList = await client.list(options);
26+
*```
27+
*
28+
* Asynchronous iteration is also supported. This will fetch additional pages if needed until all items are
29+
* retrieved.
30+
*
31+
* ```javascript
32+
* for await (const singleItem of client.list(options)) {...}
33+
* ```
2134
*/
22-
async list(
35+
list(
2336
options: KeyValueStoreCollectionClientListOptions = {},
24-
): Promise<PaginatedList<KeyValueStoreCollectionListResult>> {
37+
): Promise<PaginatedList<KeyValueStoreCollectionListResult>> & AsyncIterable<KeyValueStore> {
2538
ow(
2639
options,
2740
ow.object.exactShape({
@@ -32,7 +45,7 @@ export class KeyValueStoreCollectionClient extends ResourceCollectionClient {
3245
}),
3346
);
3447

35-
return this._list(options);
48+
return this._listPaginated(options);
3649
}
3750

3851
/**
@@ -49,10 +62,8 @@ export class KeyValueStoreCollectionClient extends ResourceCollectionClient {
4962
}
5063
}
5164

52-
export interface KeyValueStoreCollectionClientListOptions {
65+
export interface KeyValueStoreCollectionClientListOptions extends PaginationOptions {
5366
unnamed?: boolean;
54-
limit?: number;
55-
offset?: number;
5667
desc?: boolean;
5768
}
5869

0 commit comments

Comments
 (0)