Skip to content

Commit cba6d19

Browse files
committed
feat!: Change PaginatorHttp.values to a generator
1 parent dee9874 commit cba6d19

File tree

4 files changed

+38
-132
lines changed

4 files changed

+38
-132
lines changed

src/adapters/action/paginator-http.spec.ts

Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("PaginatorHttp", () => {
1616
it("sends a request", async () => {
1717
const paginator = new PaginatorHttp(http, "/v1/api/timelines", {
1818
foo: "bar",
19-
});
19+
}).values();
2020
await paginator.next();
2121
expect(http.request).toBeCalledWith({
2222
method: "GET",
@@ -43,7 +43,7 @@ describe("PaginatorHttp", () => {
4343
link: '<https://mastodon.social/api/v1/timelines/home?max_id=109382006402042919>; rel="next", <https://mastodon.social/api/v1/timelines/home?min_id=109382039876197520>; rel="prev"',
4444
}),
4545
});
46-
const paginator = new PaginatorHttp(http, "/v1/api/timelines");
46+
const paginator = new PaginatorHttp(http, "/v1/api/timelines").values();
4747
await paginator.next();
4848
await paginator.next();
4949
expect(http.request).toBeCalledWith({
@@ -67,8 +67,9 @@ describe("PaginatorHttp", () => {
6767
paginator = paginator.setDirection("prev");
6868
expect(paginator.getDirection()).toBe("prev");
6969

70-
await paginator.next();
71-
await paginator.next();
70+
const pages = paginator.values();
71+
await pages.next();
72+
await pages.next();
7273
expect(http.request).toBeCalledWith({
7374
method: "GET",
7475
search: "min_id=109382039876197520",
@@ -82,7 +83,7 @@ describe("PaginatorHttp", () => {
8283
link: '<https://mastodon.social/api/v1/timelines/home?min_id=109382039876197520>; rel="prev", <https://mastodon.social/api/v1/timelines/home?max_id=109382006402042919>; rel="next"',
8384
}),
8485
});
85-
const paginator = new PaginatorHttp(http, "/v1/api/timelines");
86+
const paginator = new PaginatorHttp(http, "/v1/api/timelines").values();
8687
await paginator.next();
8788
await paginator.next();
8889
expect(http.request).toBeCalledWith({
@@ -93,7 +94,7 @@ describe("PaginatorHttp", () => {
9394
});
9495

9596
it("returns done when next link does not exist", async () => {
96-
const paginator = new PaginatorHttp(http, "/v1/api/timelines");
97+
const paginator = new PaginatorHttp(http, "/v1/api/timelines").values();
9798
await paginator.next();
9899
const result = await paginator.next();
99100
expect(result).toEqual({
@@ -135,50 +136,6 @@ describe("PaginatorHttp", () => {
135136
});
136137
});
137138

138-
it("clones itself", async () => {
139-
const paginator1 = new PaginatorHttp(http, "/some/api", { query: "value" });
140-
const paginator2 = paginator1.clone();
141-
142-
await paginator1.next();
143-
await paginator2.next();
144-
145-
expect(http.request).toBeCalledTimes(2);
146-
expect(http.request).nthCalledWith(1, {
147-
method: "GET",
148-
search: { query: "value" },
149-
path: "/some/api",
150-
});
151-
expect(http.request).nthCalledWith(2, {
152-
method: "GET",
153-
search: { query: "value" },
154-
path: "/some/api",
155-
});
156-
157-
expect(paginator1).not.toBe(paginator2);
158-
});
159-
160-
it("terminates pagination by return", async () => {
161-
const paginator = new PaginatorHttp(http, "/v1/api/timelines");
162-
await paginator.return();
163-
const result = await paginator.next();
164-
expect(result).toEqual({
165-
done: true,
166-
value: undefined,
167-
});
168-
});
169-
170-
it("terminates pagination by throw", async () => {
171-
const paginator = new PaginatorHttp(http, "/v1/api/timelines");
172-
await expect(() => paginator.throw("some error")).rejects.toBe(
173-
"some error",
174-
);
175-
const result = await paginator.next();
176-
expect(result).toEqual({
177-
done: true,
178-
value: undefined,
179-
});
180-
});
181-
182139
it("parse array in url query string correctly", async () => {
183140
http.request.mockReturnValue({
184141
headers: new Headers({
@@ -187,7 +144,7 @@ describe("PaginatorHttp", () => {
187144
});
188145
const paginator = new PaginatorHttp(http, "/v1/api/notifications", {
189146
types: ["mention"],
190-
});
147+
}).values();
191148
await paginator.next();
192149
await paginator.next();
193150
expect(http.request).toBeCalledWith({

src/adapters/action/paginator-http.ts

Lines changed: 24 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,49 +8,32 @@ export class PaginatorHttp<Entity, Params = undefined>
88
{
99
constructor(
1010
private readonly http: Http,
11-
private nextPath?: string,
12-
private nextParams?: Params | string,
11+
private path?: string,
12+
private params?: Params | string,
1313
private readonly meta?: HttpMetaParams,
1414
private readonly direction: mastodon.Direction = "next",
1515
) {}
1616

17-
async next(): Promise<IteratorResult<Entity, undefined>> {
18-
if (!this.nextPath) {
19-
return { done: true, value: undefined };
20-
}
21-
22-
const response = await this.http.request({
23-
method: "GET",
24-
path: this.nextPath,
25-
search: this.nextParams as Record<string, unknown>,
26-
...this.meta,
27-
});
17+
async *values(): AsyncIterableIterator<Entity> {
18+
let path = this.path;
19+
let params = this.params;
2820

29-
const nextUrl = this.getLink(response.headers.get("link"));
30-
this.nextPath = nextUrl?.pathname;
31-
this.nextParams = nextUrl?.search.replace(/^\?/, "");
21+
while (path != undefined) {
22+
const response = await this.http.request({
23+
method: "GET",
24+
path,
25+
search: params as Record<string, unknown>,
26+
...this.meta,
27+
});
3228

33-
const data = (await response.data) as Entity;
34-
35-
return {
36-
done: false,
37-
value: data,
38-
};
39-
}
29+
const nextUrl = this.getLink(response.headers.get("link"));
30+
path = nextUrl?.pathname;
31+
params = nextUrl?.search.replace(/^\?/, "");
4032

41-
async return(
42-
value?: undefined | Promise<undefined>,
43-
): Promise<IteratorResult<Entity, undefined>> {
44-
this.clear();
45-
return {
46-
done: true,
47-
value: await value,
48-
};
49-
}
33+
const data = (await response.data) as Entity;
5034

51-
async throw(e: unknown): Promise<IteratorResult<Entity, undefined>> {
52-
this.clear();
53-
throw e;
35+
yield data;
36+
}
5437
}
5538

5639
then<TResult1 = Entity, TResult2 = never>(
@@ -61,13 +44,9 @@ export class PaginatorHttp<Entity, Params = undefined>
6144
reason: unknown,
6245
) => TResult2 | PromiseLike<TResult2> = Promise.reject.bind(Promise),
6346
): Promise<TResult1 | TResult2> {
64-
// we assume the first item won't be undefined
65-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
66-
return this.next().then((value) => onfulfilled(value.value!), onrejected);
67-
}
68-
69-
values(): AsyncIterableIterator<Entity> {
70-
return this[Symbol.asyncIterator]();
47+
return this.values()
48+
.next()
49+
.then((value) => onfulfilled(value.value), onrejected);
7150
}
7251

7352
getDirection(): mastodon.Direction {
@@ -77,8 +56,8 @@ export class PaginatorHttp<Entity, Params = undefined>
7756
setDirection(direction: mastodon.Direction): PaginatorHttp<Entity, Params> {
7857
return new PaginatorHttp(
7958
this.http,
80-
this.nextPath,
81-
this.nextParams,
59+
this.path,
60+
this.params,
8261
this.meta,
8362
direction,
8463
);
@@ -89,12 +68,7 @@ export class PaginatorHttp<Entity, Params = undefined>
8968
undefined,
9069
Params | string | undefined
9170
> {
92-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
93-
return this as any as AsyncIterator<
94-
Entity,
95-
undefined,
96-
Params | string | undefined
97-
>;
71+
return this.values();
9872
}
9973

10074
private getLink(value?: string | null): URL | undefined {
@@ -109,19 +83,4 @@ export class PaginatorHttp<Entity, Params = undefined>
10983

11084
return new URL(parsed);
11185
}
112-
113-
private clear() {
114-
this.nextPath = undefined;
115-
this.nextParams = undefined;
116-
}
117-
118-
clone(): PaginatorHttp<Entity, Params> {
119-
return new PaginatorHttp(
120-
this.http,
121-
this.nextPath,
122-
this.nextParams,
123-
this.meta,
124-
this.direction,
125-
);
126-
}
12786
}

src/mastodon/paginator.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export type Direction = "next" | "prev";
22

33
// eslint-disable-next-line prettier/prettier
4-
export interface Paginator<Entity, Params = undefined> extends PromiseLike<Entity> {
4+
export interface Paginator<Entity, Params = undefined> extends PromiseLike<Entity>, AsyncIterable<Entity> {
55
/**
66
* Get the current direction of the paginator.
77
* @returns The current direction of the paginator.
@@ -15,18 +15,6 @@ export interface Paginator<Entity, Params = undefined> extends PromiseLike<Entit
1515
*/
1616
setDirection(direction: Direction): Paginator<Entity, Params>;
1717

18-
/**
19-
* Clones the paginator.
20-
* @returns A new paginator with the same direction and parameters.
21-
*/
22-
clone(): Paginator<Entity, Params>;
23-
24-
next(params?: Params | string): Promise<IteratorResult<Entity, undefined>>;
25-
return(
26-
value: undefined | PromiseLike<undefined>,
27-
): Promise<IteratorResult<Entity, undefined>>;
28-
throw(e?: unknown): Promise<IteratorResult<Entity, undefined>>;
29-
3018
values(): AsyncIterableIterator<Entity>;
3119

3220
[Symbol.asyncIterator](): AsyncIterator<

tests/rest/v2/search.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ it("searches", async () => {
1313

1414
it("can be iterated over", async () => {
1515
await using session = await sessions.acquire();
16-
const results = session.rest.v2.search.list({
17-
q: "mastodon",
18-
});
16+
const results = session.rest.v2.search
17+
.list({
18+
q: "mastodon",
19+
})
20+
.values();
1921

2022
const p1 = await results.next();
2123

0 commit comments

Comments
 (0)