Skip to content

Commit 1058759

Browse files
author
williamd5
authored
Merge pull request #30 from cloudnode-pro/22-methods-to-fetch-more-pages
Implement methods to fetch different pages of the same dataset
2 parents f3eb6e7 + 7560106 commit 1058759

File tree

8 files changed

+393
-36
lines changed

8 files changed

+393
-36
lines changed

README.md

Lines changed: 60 additions & 14 deletions
Large diffs are not rendered by default.

browser/Cloudnode.js

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Cloudnode {
3131
* @param pathParams Path parameters to use in the request
3232
* @param queryParams Query parameters to use in the request
3333
* @param body Body to use in the request
34+
* @internal
3435
* @private
3536
*/
3637
async #sendRequest(operation, pathParams, queryParams, body) {
@@ -72,12 +73,61 @@ class Cloudnode {
7273
}
7374
else
7475
data = text;
75-
const res = Cloudnode.makeApiResponse(data, new Cloudnode.RawResponse(response));
76+
const res = Cloudnode.makeApiResponse(data, new Cloudnode.RawResponse(response, { operation, pathParams, queryParams, body }));
7677
if (response.ok)
7778
return res;
7879
else
7980
throw res;
8081
}
82+
/**
83+
* Get another page of paginated results
84+
* @param response Response to get a different page of
85+
* @param page Page to get
86+
* @returns The new page or null if the page is out of bounds
87+
*/
88+
async getPage(response, page) {
89+
if (page * response.limit > response.total || page < 1)
90+
return null;
91+
const query = Object.assign({}, response._response.request.queryParams);
92+
query.page = page.toString();
93+
return await this.#sendRequest(response._response.request.operation, response._response.request.pathParams, query, response._response.request.body);
94+
}
95+
/**
96+
* Get next page of paginated results
97+
* @param response Response to get the next page of
98+
* @returns The next page or null if this is the last page
99+
*/
100+
async getNextPage(response) {
101+
return await this.getPage(response, response.page + 1);
102+
}
103+
/**
104+
* Get previous page of paginated results
105+
* @param response Response to get the previous page of
106+
* @returns The previous page or null if this is the first page
107+
*/
108+
async getPreviousPage(response) {
109+
return await this.getPage(response, response.page - 1);
110+
}
111+
/**
112+
* Get all other pages of paginated results and return the complete data
113+
* > **Warning:** Depending on the amount of data, this can take a long time and use a lot of memory.
114+
* @param response Response to get all pages of
115+
* @returns All of the data in 1 page
116+
*/
117+
async getAllPages(response) {
118+
const pages = new Array(Math.ceil(response.total / response.limit)).fill(null);
119+
pages[response.page - 1] = true;
120+
const promises = pages.map((page, i) => page === null ? this.getPage(response, i + 1) : true);
121+
const newPages = await Promise.all(promises.filter(page => page !== true));
122+
newPages.splice(response.page - 1, 0, response);
123+
const allPages = newPages.filter(p => p !== null);
124+
return {
125+
items: allPages.map(p => p.items).flat(),
126+
total: response.total,
127+
limit: response.limit,
128+
page: 1
129+
};
130+
}
81131
newsletter = {
82132
/**
83133
* List newsletters
@@ -258,13 +308,19 @@ class Cloudnode {
258308
* The URL of the response.
259309
*/
260310
url;
261-
constructor(response) {
311+
/**
312+
* The request params
313+
* @readonly
314+
*/
315+
request;
316+
constructor(response, request) {
262317
this.headers = Object.fromEntries(response.headers.entries());
263318
this.ok = response.ok;
264319
this.redirected = response.redirected;
265320
this.status = response.status;
266321
this.statusText = response.statusText;
267-
this.url = response.url;
322+
this.url = new URL(response.url);
323+
this.request = request;
268324
}
269325
}
270326
Cloudnode.RawResponse = RawResponse;

browser/Cloudnode.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gen/docs.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,37 @@ export function generateDocSchema (schema: Schema, config: Config, pkg: Package)
6666
});
6767
mainClass.properties.push(...operationMethods);
6868
mainClass.properties.sort((a, b) => a.displayName.localeCompare(b.displayName));
69-
mainClass.properties.unshift(new DocSchema.Method(`new ${config.name}`, `Construct a new ${config.name} API client`, [
69+
const always: DocSchema.Method[] = [];
70+
always.push(new DocSchema.Method(`new ${config.name}`, `Construct a new ${config.name} API client`, [
7071
new DocSchema.Parameter("token", "string", "API token to use for requests", false),
7172
new DocSchema.Parameter("baseUrl", "string", "Base URL of the API", false, config.baseUrl)
7273
], undefined, []));
74+
always.push(new DocSchema.Method(`${config.name}.getPage<T>`, "Get another page of paginated results", [
75+
new DocSchema.Parameter("response", `${config.name}.ApiResponse<${config.name}.PaginatedData<T>>`, "Response to get a different page of", true),
76+
new DocSchema.Parameter("page", "number", "Page to get", true)
77+
], {
78+
type: `Promise<${config.name}.ApiResponse<${config.name}.PaginatedData<T>> | null>`,
79+
description: "The new page or null if the page is out of bounds"
80+
}, []));
81+
always.push(new DocSchema.Method(`${config.name}.getNextPage<T>`, "Get next page of paginated results", [
82+
new DocSchema.Parameter("response", `${config.name}.ApiResponse<${config.name}.PaginatedData<T>>`, "Response to get the next page of", true)
83+
], {
84+
type: `Promise<${config.name}.ApiResponse<${config.name}.PaginatedData<T>> | null>`,
85+
description: "The next page or null if this is the last page"
86+
}, []));
87+
always.push(new DocSchema.Method(`${config.name}.getPreviousPage<T>`, "Get previous page of paginated results", [
88+
new DocSchema.Parameter("response", `${config.name}.ApiResponse<${config.name}.PaginatedData<T>>`, "Response to get the previous page of", true)
89+
], {
90+
type: `Promise<${config.name}.ApiResponse<${config.name}.PaginatedData<T>> | null>`,
91+
description: "The previous page or null if this is the first page"
92+
}, []));
93+
always.push(new DocSchema.Method(`${config.name}.getAllPages<T>`, "Get all other pages of paginated results and return the complete data\n> **Warning:** Depending on the amount of data, this can take a long time and use a lot of memory.", [
94+
new DocSchema.Parameter("response", `${config.name}.ApiResponse<${config.name}.PaginatedData<T>>`, "Response to get all pages of", true)
95+
], {
96+
type: `Promise<${config.name}.PaginatedData<T>>`,
97+
description: "All of the data in 1 page"
98+
}, []));
99+
mainClass.properties.unshift(...always);
73100
mainNamespace.properties.sort((a, b) => a.displayName.localeCompare(b.displayName));
74101

75102
doc.groups.push(mainClass);
@@ -144,7 +171,7 @@ export function linkType (type: string, config: Config, schema: Schema): string
144171
return link(typeName);
145172
};
146173

147-
return `<code>${fullLink(type)}</code>`;
174+
return `<code>${fullLink(type).replaceAll("<", "&lt;")}</code>`;
148175
}
149176

150177
/**

gen/templates/main.mustache

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class {{config.name}} {
3636
* @param pathParams Path parameters to use in the request
3737
* @param queryParams Query parameters to use in the request
3838
* @param body Body to use in the request
39+
* @internal
3940
* @private
4041
*/
4142
async #sendRequest<T>(operation: Schema.Operation, pathParams: Record<string, string>, queryParams: Record<string, string>, body?: any): Promise<Cloudnode.ApiResponse<T>> {
@@ -75,11 +76,63 @@ class {{config.name}} {
7576
});
7677
}
7778
else data = text as any;
78-
const res = Cloudnode.makeApiResponse(data, new Cloudnode.RawResponse(response));
79+
const res = Cloudnode.makeApiResponse(data, new Cloudnode.RawResponse(response, {operation, pathParams, queryParams, body} as const));
7980
if (response.ok) return res;
8081
else throw res;
8182
}
8283

84+
/**
85+
* Get another page of paginated results
86+
* @param response Response to get a different page of
87+
* @param page Page to get
88+
* @returns The new page or null if the page is out of bounds
89+
*/
90+
async getPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>, page: number): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null> {
91+
if (page * response.limit > response.total || page < 1) return null;
92+
const query = Object.assign({}, response._response.request.queryParams);
93+
query.page = page.toString();
94+
return await this.#sendRequest(response._response.request.operation, response._response.request.pathParams, query, response._response.request.body);
95+
}
96+
97+
/**
98+
* Get next page of paginated results
99+
* @param response Response to get the next page of
100+
* @returns The next page or null if this is the last page
101+
*/
102+
async getNextPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null> {
103+
return await this.getPage(response, response.page + 1);
104+
}
105+
106+
/**
107+
* Get previous page of paginated results
108+
* @param response Response to get the previous page of
109+
* @returns The previous page or null if this is the first page
110+
*/
111+
async getPreviousPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null> {
112+
return await this.getPage(response, response.page - 1);
113+
}
114+
115+
/**
116+
* Get all other pages of paginated results and return the complete data
117+
* > **Warning:** Depending on the amount of data, this can take a long time and use a lot of memory.
118+
* @param response Response to get all pages of
119+
* @returns All of the data in 1 page
120+
*/
121+
async getAllPages<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.PaginatedData<T>> {
122+
const pages: (true | null)[] = new Array(Math.ceil(response.total / response.limit)).fill(null);
123+
pages[response.page - 1] = true;
124+
const promises: (Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null> | true)[] = pages.map((page, i) => page === null ? this.getPage(response, i + 1) : true);
125+
const newPages = await Promise.all(promises.filter(page => page !== true));
126+
newPages.splice(response.page - 1, 0, response);
127+
const allPages = newPages.filter(p => p !== null) as Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>[];
128+
return {
129+
items: allPages.map(p => p.items).flat(),
130+
total: response.total,
131+
limit: response.limit,
132+
page: 1
133+
};
134+
}
135+
83136
{{#namespaces}}
84137
public {{name}} = {
85138
{{#operations}}
@@ -143,7 +196,7 @@ namespace {{config.name}} {
143196
/**
144197
* The page items
145198
*/
146-
items: T;
199+
items: T[];
147200
/**
148201
* The total number of items
149202
*/
@@ -190,15 +243,27 @@ namespace {{config.name}} {
190243
/**
191244
* The URL of the response.
192245
*/
193-
public readonly url: string;
246+
public readonly url: URL;
247+
248+
/**
249+
* The request params
250+
* @readonly
251+
*/
252+
public readonly request: {
253+
readonly operation: Schema.Operation;
254+
readonly pathParams: Record<string, string>;
255+
readonly queryParams: Record<string, string>;
256+
readonly body: any;
257+
};
194258

195-
public constructor(response: import("node-fetch").Response) {
259+
public constructor(response: import("node-fetch").Response, request: {operation: Schema.Operation, pathParams: Record<string, string>, queryParams: Record<string, string>, body: any}) {
196260
this.headers = Object.fromEntries(response.headers.entries());
197261
this.ok = response.ok;
198262
this.redirected = response.redirected;
199263
this.status = response.status;
200264
this.statusText = response.statusText;
201-
this.url = response.url;
265+
this.url = new URL(response.url);
266+
this.request = request;
202267
}
203268
}
204269

src/Cloudnode.d.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Schema from "../gen/Schema";
12
/**
23
* A client SDK for the Cloudnode API, written in TypeScript. [Documentation](https://github.com/cloudnode-pro/ts-client#documentation)
34
* @class
@@ -10,6 +11,32 @@ declare class Cloudnode {
1011
* @param [baseUrl="https://api.cloudnode.pro/v5/"] Base URL of the API
1112
*/
1213
constructor(token?: string, baseUrl?: string);
14+
/**
15+
* Get another page of paginated results
16+
* @param response Response to get a different page of
17+
* @param page Page to get
18+
* @returns The new page or null if the page is out of bounds
19+
*/
20+
getPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>, page: number): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null>;
21+
/**
22+
* Get next page of paginated results
23+
* @param response Response to get the next page of
24+
* @returns The next page or null if this is the last page
25+
*/
26+
getNextPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null>;
27+
/**
28+
* Get previous page of paginated results
29+
* @param response Response to get the previous page of
30+
* @returns The previous page or null if this is the first page
31+
*/
32+
getPreviousPage<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>> | null>;
33+
/**
34+
* Get all other pages of paginated results and return the complete data
35+
* > **Warning:** Depending on the amount of data, this can take a long time and use a lot of memory.
36+
* @param response Response to get all pages of
37+
* @returns All of the data in 1 page
38+
*/
39+
getAllPages<T>(response: Cloudnode.ApiResponse<Cloudnode.PaginatedData<T>>): Promise<Cloudnode.PaginatedData<T>>;
1340
newsletter: {
1441
/**
1542
* List newsletters
@@ -305,7 +332,7 @@ declare namespace Cloudnode {
305332
/**
306333
* The page items
307334
*/
308-
items: T;
335+
items: T[];
309336
/**
310337
* The total number of items
311338
*/
@@ -346,8 +373,23 @@ declare namespace Cloudnode {
346373
/**
347374
* The URL of the response.
348375
*/
349-
readonly url: string;
350-
constructor(response: import("node-fetch").Response);
376+
readonly url: URL;
377+
/**
378+
* The request params
379+
* @readonly
380+
*/
381+
readonly request: {
382+
readonly operation: Schema.Operation;
383+
readonly pathParams: Record<string, string>;
384+
readonly queryParams: Record<string, string>;
385+
readonly body: any;
386+
};
387+
constructor(response: import("node-fetch").Response, request: {
388+
operation: Schema.Operation;
389+
pathParams: Record<string, string>;
390+
queryParams: Record<string, string>;
391+
body: any;
392+
});
351393
}
352394
namespace R {
353395
class ApiResponse {

0 commit comments

Comments
 (0)