1
1
const url = require ( 'url' ) ;
2
- const { memoize} = require ( 'lodash' ) ;
2
+ const { memoize, get } = require ( 'lodash' ) ;
3
3
const Octokit = require ( '@octokit/rest' ) ;
4
4
const pRetry = require ( 'p-retry' ) ;
5
5
const Bottleneck = require ( 'bottleneck' ) ;
6
6
const urljoin = require ( 'url-join' ) ;
7
7
const HttpProxyAgent = require ( 'http-proxy-agent' ) ;
8
8
const HttpsProxyAgent = require ( 'https-proxy-agent' ) ;
9
9
10
- /**
11
- * Default exponential backoff configuration for retries.
12
- */
13
- const DEFAULT_RETRY = { retries : 3 , factor : 2 , minTimeout : 1000 } ;
14
-
15
- /**
16
- * Rate limit per API endpoints.
17
- *
18
- * See {@link https://developer.github.com/v3/search/#rate-limit|Search API rate limit}.
19
- * See {@link https://developer.github.com/v3/#rate-limiting|Rate limiting}.
20
- */
21
- const RATE_LIMITS = {
22
- search : ( 60 * 1000 ) / 30 , // 30 calls per minutes => 1 call per 2s
23
- core : ( 60 * 60 * 1000 ) / 5000 , // 5000 calls per hour => 1 call per 720ms
24
- } ;
25
-
26
- /**
27
- * Global rate limit to prevent abuse.
28
- *
29
- * See {@link https://developer.github.com/v3/guides/best-practices-for-integrators/#dealing-with-abuse-rate-limits|Dealing with abuse rate limits}
30
- */
31
- const GLOBAL_RATE_LIMIT = 1000 ;
10
+ const GH_ROUTES = require ( '@octokit/rest/lib/routes' ) ;
11
+ const { RETRY_CONF , RATE_LIMITS , GLOBAL_RATE_LIMIT } = require ( './definitions/rate-limit' ) ;
32
12
33
13
/**
34
14
* Http error codes for which to not retry.
@@ -41,35 +21,66 @@ const SKIP_RETRY_CODES = [400, 401, 403];
41
21
* @param {Array } rate The rate limit group.
42
22
* @param {String } limit The rate limits per API endpoints.
43
23
* @param {Bottleneck } globalThrottler The global throttler.
24
+ *
44
25
* @return {Bottleneck } The throller function for the given rate limit group.
45
26
*/
46
- const getThrottler = memoize ( ( rate , limit , globalThrottler ) =>
47
- new Bottleneck ( { minTime : limit [ rate ] } ) . chain ( globalThrottler )
27
+ const getThrottler = memoize ( ( rate , globalThrottler ) =>
28
+ new Bottleneck ( { minTime : get ( RATE_LIMITS , rate ) } ) . chain ( globalThrottler )
48
29
) ;
49
30
31
+ /**
32
+ * Determine if a call to a client function will trigger a read (`GET`) or a write (`POST`, `PATCH`, etc...) request.
33
+ *
34
+ * @param {String } endpoint The client API enpoint (for example the endpoint for a call to `github.repos.get` is `repos`).
35
+ * @param {String } command The client API command (for example the command for a call to `github.repos.get` is `get`).
36
+ *
37
+ * @return {String } `write` or `read` if there is rate limit configuration for this `endpoint` and `command`, `undefined` otherwise.
38
+ */
39
+ const getAccess = ( endpoint , command ) => {
40
+ const method = GH_ROUTES [ endpoint ] && GH_ROUTES [ endpoint ] [ command ] && GH_ROUTES [ endpoint ] [ command ] . method ;
41
+ const access = method && method === 'GET' ? 'read' : 'write' ;
42
+ return RATE_LIMITS [ endpoint ] [ access ] && access ;
43
+ } ;
44
+
45
+ /**
46
+ * Get the limiter identifier associated with a client API call.
47
+ *
48
+ * @param {String } endpoint The client API enpoint (for example the endpoint for a call to `github.repos.get` is `repos`).
49
+ * @param {String } command The client API command (for example the command for a call to `github.repos.get` is `get`).
50
+ *
51
+ * @return {String } A string identifying the limiter to use for this `endpoint` and `command` (e.g. `search` or `core.write`).
52
+ */
53
+ const getLimitKey = ( endpoint , command ) => {
54
+ return endpoint
55
+ ? [ endpoint , RATE_LIMITS [ endpoint ] && getAccess ( endpoint , command ) ] . filter ( Boolean ) . join ( '.' )
56
+ : RATE_LIMITS [ command ]
57
+ ? command
58
+ : 'core' ;
59
+ } ;
60
+
50
61
/**
51
62
* Create a`handler` for a `Proxy` wrapping an Octokit instance to:
52
63
* - Recursively wrap the child objects of the Octokit instance in a `Proxy`
53
64
* - Throttle and retry the Octokit instance functions
54
65
*
55
- * @param {Object } retry The configuration to pass to `p-retry`.
56
- * @param {Array } limit The rate limits per API endpoints.
57
66
* @param {Throttler } globalThrottler The throller function for the global rate limit.
58
- * @param {String } endpoint The API endpoint to handle.
67
+ * @param {String } limitKey The key to find the limit rate for the API endpoint and method.
68
+ *
59
69
* @return {Function } The `handler` for a `Proxy` wrapping an Octokit instance.
60
70
*/
61
- const handler = ( retry , limit , globalThrottler , endpoint ) => ( {
71
+ const handler = ( globalThrottler , limitKey ) => ( {
62
72
/**
63
73
* If the target has the property as own, determine the rate limit based on the property name and recursively wrap the value in a `Proxy`. Otherwise returns the property value.
64
74
*
65
75
* @param {Object } target The target object.
66
76
* @param {String } name The name of the property to get.
67
77
* @param {Any } receiver The `Proxy` object.
78
+ *
68
79
* @return {Any } The property value or a `Proxy` of the property value.
69
80
*/
70
81
get : ( target , name , receiver ) =>
71
82
Reflect . apply ( Object . prototype . hasOwnProperty , target , [ name ] )
72
- ? new Proxy ( target [ name ] , handler ( retry , limit , globalThrottler , endpoint || name ) )
83
+ ? new Proxy ( target [ name ] , handler ( globalThrottler , getLimitKey ( limitKey , name ) ) )
73
84
: Reflect . get ( target , name , receiver ) ,
74
85
75
86
/**
@@ -78,11 +89,11 @@ const handler = (retry, limit, globalThrottler, endpoint) => ({
78
89
* @param {Function } func The target function.
79
90
* @param {Any } that The this argument for the call.
80
91
* @param {Array } args The list of arguments for the call.
92
+ *
81
93
* @return {Promise<Any> } The result of the function called.
82
94
*/
83
95
apply : ( func , that , args ) => {
84
- const throttler = getThrottler ( limit [ endpoint ] ? endpoint : 'core' , limit , globalThrottler ) ;
85
-
96
+ const throttler = getThrottler ( limitKey , globalThrottler ) ;
86
97
return pRetry ( async ( ) => {
87
98
try {
88
99
return await throttler . wrap ( func ) ( ...args ) ;
@@ -92,19 +103,11 @@ const handler = (retry, limit, globalThrottler, endpoint) => ({
92
103
}
93
104
throw err ;
94
105
}
95
- } , retry ) ;
106
+ } , RETRY_CONF ) ;
96
107
} ,
97
108
} ) ;
98
109
99
- module . exports = ( {
100
- githubToken,
101
- githubUrl,
102
- githubApiPathPrefix,
103
- proxy,
104
- retry = DEFAULT_RETRY ,
105
- limit = RATE_LIMITS ,
106
- globalLimit = GLOBAL_RATE_LIMIT ,
107
- } ) => {
110
+ module . exports = ( { githubToken, githubUrl, githubApiPathPrefix, proxy} = { } ) => {
108
111
const baseUrl = githubUrl && urljoin ( githubUrl , githubApiPathPrefix ) ;
109
112
const github = new Octokit ( {
110
113
baseUrl,
@@ -115,5 +118,5 @@ module.exports = ({
115
118
: undefined ,
116
119
} ) ;
117
120
github . authenticate ( { type : 'token' , token : githubToken } ) ;
118
- return new Proxy ( github , handler ( retry , limit , new Bottleneck ( { minTime : globalLimit } ) ) ) ;
121
+ return new Proxy ( github , handler ( new Bottleneck ( { minTime : GLOBAL_RATE_LIMIT } ) ) ) ;
119
122
} ;
0 commit comments