Skip to content

Commit 9b5f03b

Browse files
feat: maintain and print notion API stats
1 parent 137e6d9 commit 9b5f03b

File tree

2 files changed

+74
-47
lines changed

2 files changed

+74
-47
lines changed

src/NotionApiFacade.ts

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,28 @@ const debug = require("debug")("notion-api");
1313
export class NotionApiFacade {
1414
private readonly client: Client;
1515

16+
private readonly stats = {
17+
totalRequests: 0,
18+
totalRetries: 0,
19+
retriesByErrorCode: new Map<string, number>(),
20+
};
21+
1622
constructor(notionApiToken: string) {
1723
this.client = new Client({
1824
auth: notionApiToken,
1925
});
2026
}
2127

2228
async retrieveDatabase(databaseId: string) {
23-
return await withRetry(async () =>
29+
return await this.withRetry(async () =>
2430
this.client.databases.retrieve({
2531
database_id: databaseId,
2632
})
2733
);
2834
}
2935

3036
async queryDatabase(query: DatabasesQueryParameters) {
31-
const result = await withRetry(
37+
const result = await this.withRetry(
3238
async () => await this.client.databases.query(query)
3339
); // todo: paging
3440

@@ -42,13 +48,13 @@ export class NotionApiFacade {
4248
}
4349

4450
async retrievePage(pageId: string) {
45-
return await withRetry(
51+
return await this.withRetry(
4652
async () => await this.client.pages.retrieve({ page_id: pageId })
4753
);
4854
}
4955

5056
async listBlockChildren(blockId: string) {
51-
const result = await withRetry(
57+
const result = await this.withRetry(
5258
async () =>
5359
await this.client.blocks.children.list({
5460
block_id: blockId,
@@ -63,52 +69,71 @@ export class NotionApiFacade {
6369

6470
return result;
6571
}
66-
}
6772

73+
printStats() {
74+
console.log("Notion API request statistics", this.stats);
75+
}
6876

69-
function sleep(ms: number) {
70-
return new Promise(resolve => setTimeout(resolve, ms));
71-
}
72-
73-
async function withRetry<T>(
74-
f: () => Promise<T>,
75-
maxRetries: number = 3,
76-
retriableApiErrorCodes: APIErrorCode[] = [
77-
APIErrorCode.ServiceUnavailable,
78-
APIErrorCode.RateLimited,
79-
],
80-
retriableUnknownHTTPStatusCodes: number[] = [502]
81-
) {
82-
let lastError: any;
83-
84-
for (let i = 1; i <= maxRetries; i++) {
85-
try {
86-
return await f();
87-
} catch (error: any) {
88-
lastError = error;
89-
const isRetriable =
90-
(APIResponseError.isAPIResponseError(error) &&
91-
error.code in retriableApiErrorCodes) ||
92-
(UnknownHTTPResponseError.isUnknownHTTPResponseError(error) &&
93-
error.status in retriableUnknownHTTPStatusCodes) ||
94-
(RequestTimeoutError.isRequestTimeoutError(error));
95-
96-
if (!isRetriable) {
97-
// throw any other error immediately
98-
throw error;
77+
private async withRetry<T>(
78+
f: () => Promise<T>,
79+
maxRetries: number = 3,
80+
retriableApiErrorCodes: APIErrorCode[] = [
81+
APIErrorCode.ServiceUnavailable,
82+
APIErrorCode.RateLimited,
83+
],
84+
retriableUnknownHTTPStatusCodes: number[] = [502]
85+
) {
86+
let lastError: any;
87+
88+
for (let i = 1; i <= maxRetries; i++) {
89+
try {
90+
this.stats.totalRequests++;
91+
return await f();
92+
} catch (error: any) {
93+
lastError = error;
94+
95+
const apiError = APIResponseError.isAPIResponseError(error) && error;
96+
const unknownError =
97+
UnknownHTTPResponseError.isUnknownHTTPResponseError(error) && error;
98+
const timeoutError =
99+
RequestTimeoutError.isRequestTimeoutError(error) && error;
100+
101+
const isRetriable =
102+
(apiError && error.code in retriableApiErrorCodes) ||
103+
(unknownError && error.status in retriableUnknownHTTPStatusCodes) ||
104+
timeoutError;
105+
106+
if (!isRetriable) {
107+
// throw any other error immediately
108+
throw error;
109+
}
110+
111+
this.stats.totalRetries++;
112+
const key =
113+
(apiError && apiError.code) ||
114+
(unknownError && `${unknownError.code}.${unknownError.status}`) ||
115+
(timeoutError && `${timeoutError.code}`) ||
116+
"unknown";
117+
118+
const count = this.stats.retriesByErrorCode.get(key) || 0;
119+
this.stats.retriesByErrorCode.set(key, count + 1);
120+
121+
debug(
122+
`Notion API request failed with error ${error.code}, ${
123+
maxRetries - i
124+
} retries left`
125+
);
126+
127+
await sleep(1000 * i); // chosen by fair dice roll
99128
}
100-
101-
debug(
102-
`Notion API request failed with error ${error.code}, ${
103-
maxRetries - i
104-
} retries left`
105-
);
106-
107-
await sleep(1000 * i); // chosen by fair dice roll
108129
}
130+
131+
throw new Error(
132+
`Failed to execute Notion API request, even after ${maxRetries} retries. Original error was ${lastError}`
133+
);
109134
}
135+
}
110136

111-
throw new Error(
112-
`Failed to execute Notion API request, even after ${maxRetries} retries. Original error was ${lastError}`
113-
);
114-
}
137+
function sleep(ms: number) {
138+
return new Promise((resolve) => setTimeout(resolve, ms));
139+
}

src/sync.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,7 @@ export async function sync(notionApiToken: string, config: SyncConfig) {
5858

5959
const rendered = deferredRenderer.getRenderedPages();
6060

61+
publicApi.printStats();
62+
6163
return rendered;
6264
}

0 commit comments

Comments
 (0)