@@ -13,22 +13,28 @@ const debug = require("debug")("notion-api");
1313export 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+ }
0 commit comments