1- import { Client } from '@notionhq/client' ;
1+ import {
2+ APIErrorCode , APIResponseError , Client , RequestTimeoutError , UnknownHTTPResponseError
3+ } from '@notionhq/client' ;
24import { DatabasesQueryParameters } from '@notionhq/client/build/src/api-endpoints' ;
35
6+ const debug = require ( "debug" ) ( "notion-api" ) ;
7+
48/**
59 * A common facade for interaction with notion API and handling common concerns (esp. retry on 502/rate limits).
610 *
@@ -16,13 +20,17 @@ export class NotionApiFacade {
1620 }
1721
1822 async retrieveDatabase ( databaseId : string ) {
19- return await this . client . databases . retrieve ( {
20- database_id : databaseId ,
21- } ) ;
23+ return await withRetry ( async ( ) =>
24+ this . client . databases . retrieve ( {
25+ database_id : databaseId ,
26+ } )
27+ ) ;
2228 }
2329
2430 async queryDatabase ( query : DatabasesQueryParameters ) {
25- const result = await this . client . databases . query ( query ) ; // todo: paging
31+ const result = await withRetry (
32+ async ( ) => await this . client . databases . query ( query )
33+ ) ; // todo: paging
2634
2735 if ( result . next_cursor ) {
2836 throw new Error (
@@ -34,11 +42,18 @@ export class NotionApiFacade {
3442 }
3543
3644 async retrievePage ( pageId : string ) {
37- return await this . client . pages . retrieve ( { page_id : pageId } ) ;
45+ return await withRetry (
46+ async ( ) => await this . client . pages . retrieve ( { page_id : pageId } )
47+ ) ;
3848 }
3949
4050 async listBlockChildren ( blockId : string ) {
41- const result = await this . client . blocks . children . list ( { block_id : blockId } ) ; // todo: paging here?
51+ const result = await withRetry (
52+ async ( ) =>
53+ await this . client . blocks . children . list ( {
54+ block_id : blockId ,
55+ } )
56+ ) ; // todo: paging here?
4257
4358 if ( result . next_cursor ) {
4459 throw new Error (
@@ -49,3 +64,51 @@ export class NotionApiFacade {
4964 return result ;
5065 }
5166}
67+
68+
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 ;
99+ }
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
108+ }
109+ }
110+
111+ throw new Error (
112+ `Failed to execute Notion API request, even after ${ maxRetries } retries. Original error was ${ lastError } `
113+ ) ;
114+ }
0 commit comments