Skip to content

Commit 6aeb500

Browse files
authored
Merge pull request #166 from takker99/deal-with-push-errors
feat(websocket): websocketから返されるcommit errorを適切に処理する
2 parents b7854da + b66581f commit 6aeb500

12 files changed

+313
-421
lines changed

browser/websocket/_codeBlock.ts

-29
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import { Change, Socket, wrap } from "../../deps/socket.ts";
2-
import { Page } from "../../deps/scrapbox-rest.ts";
31
import { TinyCodeBlock } from "../../rest/getCodeBlocks.ts";
4-
import { getProjectId, getUserId } from "./id.ts";
5-
import { pushWithRetry } from "./_fetch.ts";
62

73
/** コードブロックのタイトル行の情報を保持しておくためのinterface */
84
export interface CodeTitle {
@@ -11,31 +7,6 @@ export interface CodeTitle {
117
indent: number;
128
}
139

14-
/** コミットを送信する一連の処理 */
15-
export const applyCommit = async (
16-
commits: Change[],
17-
page: Page,
18-
projectName: string,
19-
pageTitle: string,
20-
socket: Socket,
21-
userId?: string,
22-
): ReturnType<typeof pushWithRetry> => {
23-
const [projectId, userId_] = await Promise.all([
24-
getProjectId(projectName),
25-
userId ?? getUserId(),
26-
]);
27-
const { request } = wrap(socket);
28-
return await pushWithRetry(request, commits, {
29-
parentId: page.commitId,
30-
projectId: projectId,
31-
pageId: page.id,
32-
userId: userId_,
33-
project: projectName,
34-
title: pageTitle,
35-
retry: 3,
36-
});
37-
};
38-
3910
/** コードブロックのタイトル行から各種プロパティを抽出する
4011
*
4112
* @param lineText {string} 行テキスト

browser/websocket/_fetch.ts

-97
This file was deleted.

browser/websocket/deletePage.ts

+12-40
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,22 @@
1-
import { Socket, socketIO, wrap } from "../../deps/socket.ts";
2-
import { connect, disconnect } from "./socket.ts";
3-
import { getProjectId, getUserId } from "./id.ts";
4-
import { pull } from "./pull.ts";
5-
import { pushWithRetry } from "./_fetch.ts";
1+
import { push, PushOptions, RetryError } from "./push.ts";
2+
import { Result } from "../../rest/util.ts";
63

7-
export interface DeletePageOptions {
8-
socket?: Socket;
9-
}
4+
export type DeletePageOptions = PushOptions;
105

116
/** 指定したページを削除する
127
*
138
* @param project 削除したいページのproject
149
* @param title 削除したいページのタイトル
15-
* @param options 使用したいSocketがあれば指定する
10+
* @param options
1611
*/
17-
export const deletePage = async (
12+
export const deletePage = (
1813
project: string,
1914
title: string,
2015
options?: DeletePageOptions,
21-
): Promise<void> => {
22-
const [
23-
{ id: pageId, commitId: parentId, persistent },
24-
projectId,
25-
userId,
26-
] = await Promise.all([
27-
pull(project, title),
28-
getProjectId(project),
29-
getUserId(),
30-
]);
31-
32-
if (!persistent) return;
33-
34-
const injectedSocket = options?.socket;
35-
const socket = injectedSocket ?? await socketIO();
36-
await connect(socket);
37-
const { request } = wrap(socket);
38-
try {
39-
await pushWithRetry(request, [{ deleted: true }], {
40-
projectId,
41-
pageId,
42-
parentId,
43-
userId,
44-
project,
45-
title,
46-
});
47-
} finally {
48-
if (!injectedSocket) await disconnect(socket);
49-
}
50-
};
16+
): Promise<Result<string, RetryError>> =>
17+
push(
18+
project,
19+
title,
20+
(page) => page.persistent ? [{ deleted: true }] : [],
21+
options,
22+
);

browser/websocket/makeChanges.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import type { Change } from "../../deps/socket.ts";
55
import { toTitleLc } from "../../title.ts";
66
import { parseYoutube } from "../../parser/youtube.ts";
77

8-
export interface Init {
8+
export interface Init extends Page {
99
userId: string;
10-
page: Page;
1110
}
1211
export function* makeChanges(
1312
left: Pick<Line, "text" | "id">[],
1413
right: string[],
15-
{ userId, page }: Init,
14+
{ userId, ...page }: Init,
1615
): Generator<Change, void, unknown> {
1716
// 改行文字が入るのを防ぐ
1817
const right_ = right.flatMap((text) => text.split("\n"));

browser/websocket/patch.ts

+26-75
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import { Socket, socketIO, wrap } from "../../deps/socket.ts";
2-
import { connect, disconnect } from "./socket.ts";
3-
import { getProjectId, getUserId } from "./id.ts";
1+
import { Change, DeletePageChange, PinChange } from "../../deps/socket.ts";
42
import { makeChanges } from "./makeChanges.ts";
5-
import { pull } from "./pull.ts";
63
import { Line, Page } from "../../deps/scrapbox-rest.ts";
7-
import { pushCommit, pushWithRetry } from "./_fetch.ts";
4+
import { push, PushOptions, RetryError } from "./push.ts";
5+
import { suggestUnDupTitle } from "./suggestUnDupTitle.ts";
6+
import { Result } from "../../rest/util.ts";
87

9-
export interface PatchOptions {
10-
socket?: Socket;
11-
}
8+
export type PatchOptions = PushOptions;
129

1310
export interface PatchMetadata extends Page {
1411
/** 書き換えを再試行した回数
1512
*
1613
* 初回は`0`で、再試行するたびに増える
1714
*/
18-
retry: number;
15+
attempts: number;
1916
}
2017

2118
/** ページ全体を書き換える
@@ -27,77 +24,31 @@ export interface PatchMetadata extends Page {
2724
* @param update 書き換え後の本文を作成する函数。引数には現在の本文が渡される。空配列を返すとページが削除される。undefinedを返すと書き換えを中断する
2825
* @param options 使用したいSocketがあれば指定する
2926
*/
30-
export const patch = async (
27+
export const patch = (
3128
project: string,
3229
title: string,
3330
update: (
3431
lines: Line[],
3532
metadata: PatchMetadata,
3633
) => string[] | undefined | Promise<string[] | undefined>,
3734
options?: PatchOptions,
38-
): Promise<void> => {
39-
const [
40-
page_,
41-
projectId,
42-
userId,
43-
] = await Promise.all([
44-
pull(project, title),
45-
getProjectId(project),
46-
getUserId(),
47-
]);
48-
49-
let page = page_;
50-
51-
const injectedSocket = options?.socket;
52-
const socket = injectedSocket ?? await socketIO();
53-
await connect(socket);
54-
try {
55-
const { request } = wrap(socket);
56-
57-
// 3回retryする
58-
for (let retry = 0; retry < 3; retry++) {
59-
try {
60-
const pending = update(page.lines, { ...page, retry });
61-
const newLines = pending instanceof Promise ? await pending : pending;
62-
63-
if (!newLines) return;
64-
65-
if (newLines.length === 0) {
66-
await pushWithRetry(request, [{ deleted: true }], {
67-
projectId,
68-
pageId: page.id,
69-
parentId: page.commitId,
70-
userId,
71-
project,
72-
title,
73-
});
74-
}
75-
76-
const changes = [
77-
...makeChanges(page.lines, newLines, { userId, page }),
78-
];
79-
await pushCommit(request, changes, {
80-
parentId: page.commitId,
81-
projectId,
82-
pageId: page.id,
83-
userId,
84-
});
85-
break;
86-
} catch (_e: unknown) {
87-
if (retry === 2) {
88-
throw Error("Faild to retry pushing.");
89-
}
90-
console.log(
91-
"Faild to push a commit. Retry after pulling new commits",
92-
);
93-
try {
94-
page = await pull(project, title);
95-
} catch (e: unknown) {
96-
throw e;
97-
}
35+
): Promise<Result<string, RetryError>> =>
36+
push(
37+
project,
38+
title,
39+
async (page, attempts, prev, reason) => {
40+
if (reason === "DuplicateTitleError") {
41+
const fallbackTitle = suggestUnDupTitle(title);
42+
return prev.map((change) => {
43+
if ("title" in change) change.title = fallbackTitle;
44+
return change;
45+
}) as Change[] | [DeletePageChange] | [PinChange];
9846
}
99-
}
100-
} finally {
101-
if (!injectedSocket) await disconnect(socket);
102-
}
103-
};
47+
const pending = update(page.lines, { ...page, attempts });
48+
const newLines = pending instanceof Promise ? await pending : pending;
49+
if (newLines === undefined) return [];
50+
if (newLines.length === 0) return [{ deleted: true }];
51+
return [...makeChanges(page.lines, newLines, page)];
52+
},
53+
options,
54+
);

0 commit comments

Comments
 (0)