Skip to content

Commit 99e24fc

Browse files
authored
Merge pull request #30 from takker99/add-link
✨ Implement wrappers of /api/pages/:projectname/search/titles
2 parents f6ad85a + 4dca1b0 commit 99e24fc

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

deps/scrapbox.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type {
1313
Page,
1414
PageList,
1515
Scrapbox,
16+
SearchedTitle,
1617
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
1718
import type { Page } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
1819
export * from "https://esm.sh/@progfay/[email protected]";

rest/link.ts

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type { ErrorLike, SearchedTitle } from "../deps/scrapbox.ts";
2+
import { cookie } from "./auth.ts";
3+
import { UnexpectedResponseError } from "./error.ts";
4+
import { tryToErrorLike } from "../is.ts";
5+
import { BaseOptions, Result, setDefaults } from "./util.ts";
6+
7+
export interface GetLinksOptions extends BaseOptions {
8+
/** 次のリンクリストを示すID */
9+
followingId?: string;
10+
}
11+
12+
/** 指定したprojectのリンクデータを取得する
13+
*
14+
* @param project データを取得したいproject
15+
*/
16+
export const getLinks = async (
17+
project: string,
18+
options?: GetLinksOptions,
19+
): Promise<
20+
Result<{
21+
pages: SearchedTitle[];
22+
followingId: string;
23+
}, ErrorLike>
24+
> => {
25+
const { sid, hostName, fetch, followingId } = setDefaults(options ?? {});
26+
const path = `https://${hostName}/api/pages/${project}/search/titles${
27+
followingId ? `?followingId=${followingId}` : ""
28+
}`;
29+
30+
const res = await fetch(
31+
path,
32+
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
33+
);
34+
35+
if (!res.ok) {
36+
const text = await res.text();
37+
const value = tryToErrorLike(text);
38+
if (!value) {
39+
throw new UnexpectedResponseError({
40+
path: new URL(path),
41+
...res,
42+
body: text,
43+
});
44+
}
45+
return { ok: false, value };
46+
}
47+
const pages = (await res.json()) as SearchedTitle[];
48+
return {
49+
ok: true,
50+
value: { pages, followingId: res.headers.get("X-following-id") ?? "" },
51+
};
52+
};
53+
54+
/** 指定したprojectの全てのリンクデータを取得する
55+
*
56+
* responseで返ってきたリンクデータの塊ごとに返す
57+
*
58+
* @param project データを取得したいproject
59+
* @return 認証が通らなかったらエラーを、通ったらasync generatorを返す
60+
*/
61+
export const readLinksBulk = async (
62+
project: string,
63+
options?: BaseOptions,
64+
): Promise<ErrorLike | AsyncGenerator<SearchedTitle[], void, unknown>> => {
65+
const first = await getLinks(project, options);
66+
if (!first.ok) return first.value;
67+
68+
return async function* () {
69+
yield first.value.pages;
70+
let followingId = first.value.followingId;
71+
72+
while (!followingId) {
73+
const result = await getLinks(project, { followingId, ...options });
74+
75+
// すでに認証は通っているので、ここでエラーになるはずがない
76+
if (!result.ok) {
77+
throw new Error("The authorization cannot be unavailable");
78+
}
79+
yield result.value.pages;
80+
followingId = result.value.followingId;
81+
}
82+
}();
83+
};
84+
85+
/** 指定したprojectの全てのリンクデータを取得し、一つづつ返す
86+
*
87+
* @param project データを取得したいproject
88+
* @return 認証が通らなかったらエラーを、通ったらasync generatorを返す
89+
*/
90+
export const readLinks = async (
91+
project: string,
92+
options?: BaseOptions,
93+
): Promise<ErrorLike | AsyncGenerator<SearchedTitle, void, unknown>> => {
94+
const reader = await readLinksBulk(project, options);
95+
if ("name" in reader) return reader;
96+
97+
return async function* () {
98+
for await (const titles of reader) {
99+
for (const title of titles) {
100+
yield title;
101+
}
102+
}
103+
}();
104+
};

0 commit comments

Comments
 (0)