Skip to content

Commit ec34c84

Browse files
committed
feat(api): Make pagination in list function concurrent and add poolLimit option
1 parent b53e29a commit ec34c84

File tree

3 files changed

+79
-29
lines changed

3 files changed

+79
-29
lines changed

api/pages/project.ts

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ import type {
77
} from "@cosense/types/rest";
88
import { type BaseOptions, setDefaults } from "../../util.ts";
99
import { cookie } from "../../rest/auth.ts";
10-
import type { ResponseOfEndpoint } from "../../targeted_response.ts";
10+
import type {
11+
ResponseOfEndpoint,
12+
TargetedResponse,
13+
} from "../../targeted_response.ts";
1114
import {
1215
type HTTPError,
1316
makeError,
1417
makeHTTPError,
1518
type TypedError,
1619
} from "../../error.ts";
20+
import { pooledMap } from "@std/async/pool";
21+
import { range } from "@core/iterutil/range";
22+
import { flatten } from "@core/iterutil/async/flatten";
1723

18-
/** Options for {@linkcode listPages} */
24+
/** Options for {@linkcode get} */
1925
export interface ListPagesOption<R extends Response | undefined>
2026
extends BaseOptions<R> {
2127
/** the sort of page list to return
@@ -100,6 +106,16 @@ export const get = <R extends Response | undefined = Response>(
100106
}, R>
101107
>;
102108

109+
/** Options for {@linkcode list} */
110+
export interface ListPagesStreamOption<R extends Response | undefined>
111+
extends ListPagesOption<R> {
112+
/** The number of requests to make concurrently
113+
*
114+
* @default {3}
115+
*/
116+
poolLimit?: number;
117+
}
118+
103119
/**
104120
* Lists pages from a given `project` with pagination
105121
*
@@ -109,31 +125,59 @@ export const get = <R extends Response | undefined = Response>(
109125
*/
110126
export async function* list(
111127
project: string,
112-
options?: ListPagesOption<Response>,
128+
options?: ListPagesStreamOption<Response>,
113129
): AsyncGenerator<BasePage, void, unknown> {
114-
const props = { ...(options ?? {}), skip: options?.skip ?? 0 };
115-
while (true) {
116-
const response = await get(project, props);
117-
switch (response.status) {
118-
case 200:
119-
break;
120-
case 401:
121-
case 403:
122-
case 404: {
123-
const error = await response.json();
124-
throw makeError(error.name, error.message) satisfies TypedError<
125-
"NotLoggedInError" | "NotMemberError" | "NotFoundError"
126-
>;
127-
}
128-
default:
129-
throw makeHTTPError(response) satisfies HTTPError;
130+
const props = {
131+
...(options ?? {}),
132+
skip: options?.skip ?? 0,
133+
limit: options?.limit ?? 100,
134+
};
135+
const response = await ensureResponse(await get(project, props));
136+
const list = await response.json();
137+
yield* list.pages;
138+
139+
const limit = list.limit;
140+
const skip = list.skip + limit;
141+
const times = Math.ceil((list.count - skip) / limit);
142+
143+
yield* flatten(
144+
pooledMap(
145+
options?.poolLimit ?? 3,
146+
range(0, times - 1),
147+
async (i) => {
148+
const response = await ensureResponse(
149+
await get(project, { ...props, skip: skip + i * limit, limit }),
150+
);
151+
const list = await response.json();
152+
return list.pages;
153+
},
154+
),
155+
);
156+
}
157+
158+
const ensureResponse = async (
159+
response: ResponseOfEndpoint<{
160+
200: PageList;
161+
404: NotFoundError;
162+
401: NotLoggedInError;
163+
403: NotMemberError;
164+
}, Response>,
165+
): Promise<TargetedResponse<200, PageList>> => {
166+
switch (response.status) {
167+
case 200:
168+
return response;
169+
case 401:
170+
case 403:
171+
case 404: {
172+
const error = await response.json();
173+
throw makeError(error.name, error.message) satisfies TypedError<
174+
"NotLoggedInError" | "NotMemberError" | "NotFoundError"
175+
>;
130176
}
131-
const list = await response.json();
132-
yield* list.pages;
133-
props.skip += props.limit ?? 100;
134-
if (list.skip + list.limit >= list.count) break;
177+
default:
178+
throw makeHTTPError(response) satisfies HTTPError;
135179
}
136-
}
180+
};
137181

138182
export * as replace from "./project/replace.ts";
139183
export * as search from "./project/search.ts";

deno.jsonc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"./unstable-api/users/me": "./api/users/me.ts"
3939
},
4040
"imports": {
41+
"@core/iterutil": "jsr:@core/iterutil@^0.9.0",
4142
"@core/unknownutil": "jsr:@core/unknownutil@^4.0.0",
4243
"@cosense/std/browser/websocket": "./websocket/mod.ts",
4344
"@cosense/std/rest": "./rest/mod.ts",
@@ -47,7 +48,7 @@
4748
"@cosense/types/userscript": "jsr:@cosense/types@0.10/userscript",
4849
"@progfay/scrapbox-parser": "jsr:@progfay/scrapbox-parser@9",
4950
"@std/assert": "jsr:@std/assert@1",
50-
"@std/async": "jsr:@std/async@1",
51+
"@std/async": "jsr:@std/async@^1.0.11",
5152
"@std/encoding": "jsr:@std/encoding@1",
5253
"@std/http": "jsr:@std/http@^1.0.13",
5354
"@std/json": "jsr:@std/json@^1.0.0",

deno.lock

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)