startgg-helper 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "startgg-helper",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "A set of functions and classes useful to communicate with the start.gg API, using any client (YOU NEED TO PROVIDE A CLIENT YOURSELF, SEE README)",
5
5
  "main": "main.js",
6
6
  "module": "main.js",
package/readme.md CHANGED
@@ -1,16 +1,102 @@
1
1
  # start.gg Helper
2
2
 
3
- A set of functions and classes useful to interact with the start.gg GraphQL API.
3
+ A set of functions and classes useful to interact with the [start.gg GraphQL API](https://developer.start.gg/docs/intro/). It does NOT provide abstractions for the actual data retrieved from the API (events, players, sets, etc), only eases making the queries. An understanding of [GraphQL](https://graphql.org/learn/introduction/), and [start.gg's GraphQL Schema](https://smashgg-schema.netlify.app/reference/query.doc.html) are necessary to leverage the API through this package.
4
4
 
5
- ## You need to provide a client to interact with the API
6
- The functions in this package usually take a "client" argument, which is a GraphQL client object. You need to handle this object yourself, which can be done using the `graphql` package, which is not a dependency of `startgg-helper` and must be installed manually.
5
+ ## You need to provide a client to interact with the API
6
+ tl;dr : you're probably looking for `startgg-helper-node` or `startgg-helper-browser`.
7
7
 
8
- ### Why do this ?
8
+ To interact with the API, the functions in this package need (and take as argument) a "client" object, able to send requests to the GraphQL API. Such a client **is not provided by this package**. This is to ensure that this package is usable not only in a node ecosystem, but also in browser front-end code : sending requests to an API is done differently in browser-oriented code and usual NodeJS code, meaning that providing a client in this package would make it unfit for at least some purposes.
9
+ Two packages exist to solve that issue :
10
+ - `startgg-helper-node`, which includes this one and provides a client using the `graphql` package : this is for your NodeJS projects
11
+ - `startgg-helper-browser`, which includes this ont and provides a simple client relying on the `fetch` API : this is for your web projects, to be run by a browser (using a tool like `browserify` to make your node package usable on browser)
9
12
 
10
- The purpose of this package is to be usable not onlyin a node ecosystem but also in a browser project, using `browserify`. The `graphql` package does not work well with browserify, so I decided to let the user provide a client depending on their environment.
13
+ ### I still want to use this package and provide my own client
14
+ OK ! A client is actually a very simple thing : all it needs is a `request(schema, variables)` method, taking a GraphQL schema as a string and a collection of variables as an object. As long as your object exposes this method, and it correctly returns the result of the desired GraphQL request, it can be passed to `startgg-helper` functions.
11
15
 
12
- ### Ok actually you can just use startgg-helper-node or startgg-helper-browser
16
+ ## Quick Doc
17
+ [Full focumentation here](./doc.md)
13
18
 
14
- If you don't want to handle the client yourself, you can simply install
15
- - `startgg-helper-node` if you're on Node, it includes `graphql` and handles the client using this package
16
- - `startgg-helper-browser` if you're doing a browser-based app using `browserify`, which provides a custom client class based on the `fetch` API.
19
+ The basic feature of this package is the `Query` object, which represents a GraphQL Schema/Document. Its methods allow to execute the query (i.e. make a GraphQL request with the defined schema) with different variables, with automatic retries in case of failure.
20
+
21
+ ```js
22
+ const schema = `
23
+ query Test($slug: String, $page: Int, $perPage: Int){
24
+ event(slug: $slug) {
25
+ sets(page: $page, perPage: $perPage){
26
+ nodes {
27
+ id
28
+ state
29
+ }
30
+ }
31
+ }
32
+ }
33
+ `
34
+
35
+ const query = new Query(schema, 3); //3 is the default number of retries
36
+
37
+ let res = await Promise.all([
38
+ "tournament/my-tournament-1/event/ult-singles",
39
+ "tournament/my-tournament-2/event/ult-singles",
40
+ ].map(async slug => await query.execute(client, {slug, page: 1})))
41
+ ```
42
+
43
+ ### Paginated collections and queries
44
+ Paginated collections are collections not represented by a GraphQL Array, but a page system, where each query needs to specify the index and size of the page it's fecthing. See the start.gg API for more information.
45
+ A paginated collection of type T is represented by a field with a `page` and `perPage` parameter returning a Connection-style type :
46
+ ```graphql
47
+ field(page: Int, perPage: Int): TConnection
48
+
49
+ type TConnection {
50
+ pageInfo: PageInfo
51
+
52
+ nodes: [T]
53
+ }
54
+
55
+ type PageInfo {
56
+ total: Int
57
+ totalPages: Int
58
+ page: Int
59
+ perPage: Int
60
+ }
61
+ ```
62
+
63
+ The `Query.executePaginated` method can be used to query entire paginated collections, by qerying repeatedly while increasing a certain parameter of the query, which must be used as the page argument of paginated collection field in the query, which you need to point to using the path parameter, and agregating the elements of each page into one single array. The page argument will be controlled entirely by the loop and doesn't need to be included with other graphQL variables. If the pageInfo.totalPages field is included in your schema, it will be used to determine the last page ; if not, this method will stop once it receives an empty page.
64
+
65
+ Example :
66
+
67
+ ```graphql
68
+ query Example($page: Int){
69
+ topField {
70
+ field2(page: $page){
71
+ pageInfo {
72
+ totalPages
73
+ }
74
+ nodes {
75
+ ...
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+ ```js
82
+ query.executePaginated(client, {}, "topfield.field2")
83
+ ```
84
+
85
+ ### Limiters : dealing with the API rate limit
86
+ The start.gg API has a rate limit of (as of writing this) 80 requests per minute per API key. To avoid exceeding this limit, `startgg-helper` provide a client-side rate-limiting mechanism, that comes in the form of objects you can pass to request-sending functions.
87
+
88
+ ```js
89
+ const limiter = new StartGGDelayQueryLimiter(); //it is important to only create and use one in the entire program.
90
+
91
+ for (let i = 0; i < 200; i++){
92
+ query.execute(client, {slug: `tournament/my-tournament-${i}/event/ult-singles`}, limiter);
93
+ }
94
+ //queries will be delayed to avoid exceeding start.gg's rate limit
95
+ ```
96
+
97
+ there are a handful of limiter classes but only a few are useful to you (some others are here only for legacy)
98
+ - DelayQueryLimiter(rpm) : allows up to `rpm` request per minute
99
+ - StartGGDelayQueryLimiter : allows up to 60 request per minute. This is intentionally lower than the actual server-side limit, to prevent network timing mishaps from making us accidentally going over.
100
+
101
+ ### Etc
102
+ This package also provides lots of utility functions ; see the [full doc](./doc.md)
package/src/jsUtil.js CHANGED
@@ -12,11 +12,30 @@ function processObjectPath(path){
12
12
  }//jsutil
13
13
 
14
14
  /**
15
+ * Traverses nested object following a path and returns what's at the end, without throwing an error if an intermediary object-property is not found.
16
+ * The "path" argument works like a JS object access expression, starting with a property from the initial object (first parameter)
17
+ * ```js
18
+ * const obj = {a: {b: {c: 12}}};
19
+ * obj.a.b.c; //value : 12
20
+ * deep_get(obj, "a.b.c"); //returns 12
15
21
  *
16
- * @param {{}} obj
17
- * @param {string} path
18
- * @param {*} def
19
- * @returns
22
+ * obj.a.d.c; //ERROR : Cannot read properties of undefined (reading 'c')
23
+ * deep_get(obj, "a.d.c"); //returns null, no error
24
+ * deep_get(obj, "a.d.c", 15);//returns 15, default value
25
+ * ```
26
+ *
27
+ * This function supports numbers as property names, which **works with arrays**.
28
+ * ```js
29
+ * const obj = {a: [{}, {}, {b: 12}]};
30
+ *
31
+ * obj.a[2].b; value: 12
32
+ * obj.a.2.b; Syntax ERROR
33
+ * deep_get(obj, "a.2.b"); //returns 12
34
+ * ```
35
+ *
36
+ * @param {Object | Array} obj Object or array
37
+ * @param {string} path See above
38
+ * @param {*} def Value returned if the path cannot be followed to the end
20
39
  */
21
40
  export function deep_get(obj, path, def = null){
22
41
  //https://stackoverflow.com/a/8817473
@@ -29,6 +48,13 @@ export function deep_get(obj, path, def = null){
29
48
  return obj;
30
49
  };
31
50
 
51
+ /**
52
+ * Traverses nest objects following a path and sets the final property to a given vallue.
53
+ * Traversing works the same as with deep_get()
54
+ * @param {{}} obj
55
+ * @param {string} path
56
+ * @param {*} value
57
+ */
32
58
  export function deep_set(obj, path, value){
33
59
  path = processObjectPath(path);
34
60
 
@@ -49,8 +75,8 @@ export function generateUniqueID(){
49
75
  }//jsutil
50
76
 
51
77
  /**
52
- *
53
- * @param {(() => any)[]} fArray
78
+ * Returns an array containing the results of an array of functions, called without parameters. Any "undefined" result is ignored, meaning the resulting array can be smaller than the function array.
79
+ * @param {(()=>any)[]} fArray
54
80
  */
55
81
  export function fResultsArray(fArray){
56
82
  let result = [];
@@ -65,7 +91,7 @@ export function fResultsArray(fArray){
65
91
  }
66
92
 
67
93
  /**
68
- *
94
+ * Returns an array containing the results of all parameters, treated as functions, called without parameters. See fResultsArray
69
95
  * @param {...(() => any)} functions
70
96
  */
71
97
  export function fResults(...functions){
@@ -73,20 +99,26 @@ export function fResults(...functions){
73
99
  }
74
100
 
75
101
  /**
76
- *
102
+ * Serializes a value to JSON text like JSON.stringify does. (this function is just a very thin wrapper only useful for the "pretty" parameter)
77
103
  * @param {any} data
78
- * @param {boolean} pretty
104
+ * @param {boolean} pretty If true, the resulting JSON will be made to be human-readable, with 4-space indentation
79
105
  */
80
106
  export function toJSON(data, pretty){
81
107
  return JSON.stringify(data, null, pretty ? 4 : undefined);
82
108
  }//jsutil
83
109
 
110
+ /**
111
+ * Take a wild guess
112
+ * @param {any} n
113
+ */
84
114
  export function isNumber(n){
85
115
  return typeof n == "number";
86
116
  }
87
117
 
88
118
  /**
89
- *
119
+ * Converts a data to a UNIX timestamp, i.e. number of seconds since 1/1/1970 00:00. Accepts :
120
+ * - JS Date objects
121
+ * - strings and numbers : will be converted inta a Date like `new Date(d)` does. (number treated as UNIX timestamps with milisecond granularity, more complicated for strings)
90
122
  * @param {number | Date | string} d
91
123
  */
92
124
  export function toUNIXTimestamp(d){
package/src/query.js CHANGED
@@ -5,14 +5,27 @@ function isConnection(val){
5
5
  return val instanceof Object && val.nodes instanceof Array
6
6
  }
7
7
 
8
+ export class PageResult {
9
+ /**
10
+ * @param {any} result
11
+ * @param {boolean} stop
12
+ */
13
+ constructor(result, stop){
14
+ this.result = result;
15
+ this.stop = stop;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * A GraphQL Document/Schema. Allows easily batch-requesting with the same schema but different parameters.
21
+ */
8
22
  export class Query {
9
23
  #schema;
10
24
  #maxTries;
11
25
 
12
26
  /**
13
- *
14
- * @param {string} schema
15
- * @param {number?} maxTries
27
+ * @param {string} schema The GraphQL [Schema]
28
+ * @param {number?} maxTries How many retries will be performed when requesting using this query
16
29
  */
17
30
  constructor (schema, maxTries = null){
18
31
  this.#schema = schema;
@@ -25,9 +38,11 @@ export class Query {
25
38
  * @param {{[varName: string]: value}} params
26
39
  * @returns
27
40
  */
28
- #getLog(logName, params){
29
- if (!this.log) return null;
30
- let log = this.log[logName];
41
+ #getLog(logName, params, logsOverride = null){
42
+ let log;
43
+ if (logsOverride) log = logsOverride[logName];
44
+ if (this.log) log = this.log[logName] ?? log;
45
+
31
46
  if (log){
32
47
  if (typeof log == "string"){
33
48
  return log;
@@ -38,20 +53,24 @@ export class Query {
38
53
  return null;
39
54
  }
40
55
 
56
+ #getPaginatedLogOverride(params){
57
+ if (!this.paginatedLog) return null;
58
+ return this.paginatedLog(params);
59
+ }
60
+
41
61
  /**
42
62
  *
43
63
  * @param {GraphQLClient} client
44
64
  * @param {{[varName: string]: value}} params
45
65
  * @param {number} tries How many tries in are we
46
66
  * @param {TimedQuerySemaphore} limiter
47
- * @param {boolean} silentErrors
67
+ * @param {boolean} silentErrors legacy parameter, does nothing
48
68
  * @param {number} maxTries Overrides this.#maxTries
49
- * @returns
50
69
  */
51
- async #execute_(client, params, tries, limiter = null, silentErrors = false, maxTries = null){
70
+ async #execute_(client, params, tries, limiter = null, silentErrors = false, maxTries = null, logsOverride = null){
52
71
  maxTries = maxTries || this.#maxTries || 1
53
72
 
54
- console.log((this.#getLog("query", params) || "Querying ...") + " Try " + (tries + 1));
73
+ console.log((this.#getLog("query", params, logsOverride) || "Querying ...") + " Try " + (tries + 1));
55
74
  try {
56
75
  let data = await ( limiter ? limiter.execute(client, this.#schema, params) : client.request(this.#schema, params));
57
76
 
@@ -62,22 +81,21 @@ export class Query {
62
81
  console.error("Maximum number of tries reached. Throwing.", e);
63
82
  throw e;
64
83
  }
65
- console.error((this.#getLog("error", params) || "Request failed.") + ` Retrying (try ${tries + 1}). Error : `, e);
66
- return this.#execute_(client, params, tries + 1, limiter, silentErrors, maxTries);
84
+ console.error((this.#getLog("error", params, logsOverride) || "Request failed.") + ` Retrying (try ${tries + 1}). Error : `, e);
85
+ return this.#execute_(client, params, tries + 1, limiter, silentErrors, maxTries, logsOverride);
67
86
  }
68
87
  }
69
88
 
70
89
  /**
71
- * Executes the query with given parameters and client
72
- * @param {GraphQLClient} client
73
- * @param {{[varName: string]: value}} params
74
- * @param {TimedQuerySemaphore} limiter
75
- * @param {boolean} silentErrors
76
- * @param {number} maxTries Overrides the default maximum tries count for this query
90
+ * @param {GraphQLClient} client A client object ; not provided by this package, either use startgg-helper-node (or-browser) or refer to README.md
91
+ * @param {{[varName: string]: value}} params GraphQL variables
92
+ * @param {TimedQuerySemaphore} limiter A request limiter object; see TimedQuerySemaphore
93
+ * @param {boolean} silentErrors No effect, exists only for legacy purposes
94
+ * @param {number} maxTries Overrides the default maximum tries count for this query.
77
95
  * @returns
78
96
  */
79
- async execute(client, params, limiter = null, silentErrors = false, maxTries = null){
80
- return await this.#execute_(client, params, 0, limiter, silentErrors, maxTries);
97
+ async execute(client, params, limiter = null, silentErrors = false, maxTries = null, logsOverride = null){
98
+ return await this.#execute_(client, params, 0, limiter, silentErrors, maxTries, logsOverride);
81
99
  }
82
100
 
83
101
  static IWQModes = {
@@ -87,16 +105,26 @@ export class Query {
87
105
  OUT: 3
88
106
  }
89
107
 
108
+ /** @typedef {(localResult: T[], currentResult: T[], pageIndex: number) => (T[]|PageResult|boolean)?} PageCallback*/
109
+
90
110
  /**
91
- * Executes a query containing a paginated collection (of a *Connection type) repeatedly, increasing the page index each time until nothing is returned, returning an aggregation of all the pages.
92
- * @param {GraphQLClient} client
93
- * @param {{[varName: string]: value}} params
94
- * @param {string} connectionPathInQuery JSON path to the paginated collection that must aggregated in the query (JSON path : property names separated by dots)
95
- * @param {TimedQuerySemaphore} limiter
96
- * @param {{pageParamName?: string, perPageParamName?: string, perPage?: number, delay?: number, maxElements?: number, includeWholeQuery?: number, callback: (localResult: T[], page: number, totalPages: number) => T[]?}} config
97
- * @param {boolean} silentErrors
111
+ * Queries a whole paginated collection (of a *Connection type). See the start.gg API doc or this package's documentation for more info about pagniated collections. This is done through executing the query repeatedly while increasing the page index each time.
112
+ * The target collection must be pointed to by the "path" argument, and will be agregated in a single array. The schema must have a variable (whose name can be specified in parameters) that is used as the page index of the paginated field.
113
+ *
114
+ *
115
+ * @param {GraphQLClient} client A client object ; not provided by this package, either use startgg-helper-node (or-browser) or refer to README.md
116
+ * @param {{[varName: string]: value}} params GraphQL variables ; does not include the page index variable.
117
+ * @param {string} connectionPathInQuery JSON path to the paginated collection that must be aggregated in the query (JSON path : property names separated by dots, see deep_get())
118
+ * @param {TimedQuerySemaphore} limiter A request limiter object; see TimedQuerySemaphore
119
+ * @param {{pageParamName?: string, perPageParamName?: string, perPage?: number, delay?: number, maxElements?: number, includeWholeQuery?: number, callback: PageCallback?}} config
120
+ * @param config.pageParamName name of the variable representing the page index. This variable must exist in your query, and be used as an argument in a paginated collection field
121
+ * @param config.delay number of miliseconds to wait for between each query. No delay if absent.
122
+ * @param config.maxElements if present, queries will stop once this many elements have been fetched
123
+ * @param config.includeWholeQuery controls the structure of the result. If 0, only an array is returned, if 1 the whole query is returned with the `nodes` field replaced with the full array, if 3 this functions returns a tuple containing the aggregated collection and the rest of the query, 2 does both 1 and 3.
124
+ * @param config.callback a callback function called for each fetched page, with the page elements and the page index. It should return an array itself, which will be treated as the actual page (allowing users to transform the pages on the fly) ; if it does not, the paginated execution is stopped.
125
+ * @param {boolean} silentErrors No effect, exists only for legacy purposes
98
126
  * @param {number} maxTries
99
- * @returns
127
+ * @returns See config.includeWholeQuery
100
128
  */
101
129
  async executePaginated(client, params, connectionPathInQuery, limiter = null, config = {}, silentErrors = false, maxTries = null){
102
130
  let result = [];
@@ -113,14 +141,16 @@ export class Query {
113
141
  params[pageParamName] = currentPage;
114
142
  params[perPageParamName] = perPage;
115
143
 
144
+ const logsOverride = this.#getPaginatedLogOverride(params);
145
+
116
146
  let data;
117
147
  while (true){
118
148
  if (result.length >= maxElements) break;
119
149
 
120
150
  console.log("Querying page", params[pageParamName], `(${result.length} elements loaded)`);
121
- data = await this.execute(client, params, limiter, silentErrors, maxTries);
151
+ data = await this.execute(client, params, limiter, silentErrors, maxTries, logsOverride);
122
152
 
123
- if (!data) throw (this.#getLog("error", params) ?? "Request failed.") + "(in paginated execution, at page " + params[pageParamName] + ")";
153
+ if (!data) throw (this.#getLog("error", params, logsOverride) ?? "Request failed.") + "(in paginated execution, at page " + params[pageParamName] + ")";
124
154
 
125
155
  let connection = deep_get(data, connectionPathInQuery);
126
156
 
@@ -133,29 +163,58 @@ export class Query {
133
163
 
134
164
  let localResult = connection.nodes;
135
165
 
136
- if (connection.pageInfo && connection.pageInfo.totalPages){
166
+ if (connection.pageInfo && connection.pageInfo.totalPages){ //if the query contains pageInfo we use that to determine if we're at the last page
137
167
  let totalPages = connection.pageInfo.totalPages;
138
- if (!totalPages || currentPage >= totalPages) {
168
+ if (currentPage >= totalPages) {
139
169
  if (config.callback){
140
- let cbRes = config.callback(localResult, currentPage);
141
- if (cbRes) break;
142
- localResult = cbRes;
170
+ let cbRes = config.callback(localResult, currentResult, pageIndex)
171
+
172
+ if (cbRes instanceof PageResult){
173
+ if (cbRes.result){ //If the user gives us a result we take it ofc
174
+ result = cbRes.result;
175
+ } else {
176
+ result = result.concat(localResult);
177
+ }
178
+ } else if (cbRes && cbRes !== true) { //cbres isn't one that means something loop-control-related (it's over anyways so we don't care!)
179
+ result = result.concat(cbRes);
180
+ }
181
+ } else {
182
+ result = result.concat(localResult);
143
183
  }
144
- result = result.concat(localResult);
145
184
  break;
146
185
  }
147
- } else {
186
+ } else { //if not, we only know when we get an empty page
148
187
  if (localResult.length < 1) break;
149
188
  }
150
189
 
151
190
  if (config.callback){
152
191
  let cbRes = config.callback(localResult, currentPage);
153
- if (!cbRes) break;
154
- localResult = cbRes;
192
+
193
+ if (cbRes instanceof PageResult){
194
+ if (cbRes.result){ //If the user gives us a result we take it ofc
195
+ result = cbRes.result;
196
+ } else {
197
+ result = result.concat(localResult);
198
+ }
199
+ if (cbRes.stop){
200
+ break;
201
+ }
202
+ currentPage++;
203
+ } else if (!cbRes) { //"normal case" : basic callback that doesn't touch the flow of the pexecution, we concat and increment normally
204
+ result = result.concat(localResult)
205
+ currentPage++;
206
+ } else if (cbRes === true){ //callback is asking us to stop by returning true
207
+ break;
208
+ } else { //callback returns a non-boolean value : it's the local result (and we increment normally)
209
+ result = result.concat(cbRes);
210
+ currentPage++;
211
+ }
212
+
213
+ } else {
214
+ result = result.concat(localResult);
215
+ currentPage++;
155
216
  }
156
217
 
157
- result = result.concat(localResult);
158
- currentPage++;
159
218
  params[pageParamName] = currentPage;
160
219
 
161
220
  if (delay)
@@ -180,6 +239,8 @@ export class Query {
180
239
  }
181
240
 
182
241
  /**
242
+ * Deprecated, use executePaginated instead.
243
+ *
183
244
  * Executes a query containing a paginated collection, repeatedly, increasing the page index each time until nothing is returned, returning an aggregation of all the pages.
184
245
  * @param {GraphQLClient} client
185
246
  * @param {{[varName: string]: value}} params
@@ -1,7 +1,10 @@
1
1
  //i am a god of JS
2
2
 
3
- //voir la diff avec la version de sgghelper ?
4
-
3
+ /**
4
+ * Provides timing control for requests. If passed to a Query method, it will delay its execution to stay withing a defined limit.
5
+ *
6
+ * This is implemented like a semaphore automatically releasing tokens after a certain delay
7
+ */
5
8
  export class TimedQuerySemaphore {
6
9
  /**
7
10
  * @typedef {{client: any, schema: string,
@@ -25,8 +28,8 @@ export class TimedQuerySemaphore {
25
28
 
26
29
  /**
27
30
  *
28
- * @param {number} size Semaphore counter initial value
29
- * @param {number} delay
31
+ * @param {number} size Semaphore counter initial value : how many requests we can send before having to wait
32
+ * @param {number} delay Delay before each taken token is released ; how much time we wait before making new requests
30
33
  */
31
34
  constructor(size, delay){
32
35
  this.#counter = size;
@@ -67,7 +70,8 @@ export class TimedQuerySemaphore {
67
70
  }
68
71
 
69
72
  /**
70
- * Executes the given query with the given GraphQL Client
73
+ * Executes the given query with the given GraphQL Client.
74
+ * Note that this method probably shouldn't be called by anything else than Query.execute
71
75
  * @param {any} client
72
76
  * @param {string} schema
73
77
  * @param {{[varName: string]: value}} params
@@ -97,6 +101,9 @@ export class TimedQuerySemaphore {
97
101
  }
98
102
  }
99
103
 
104
+ /**
105
+ * Stops the timing manager ; always call this at the end of your program, if you dont node will think there is still work to be done and won't exit
106
+ */
100
107
  stop(){
101
108
  for (let timer of this.#timers){
102
109
  clearTimeout(timer);
@@ -104,19 +111,22 @@ export class TimedQuerySemaphore {
104
111
  }
105
112
  }
106
113
 
114
+ /**TimedQuerySemaphore that allows one request before waiting. You should use the DelayQueryLimiter instead.*/
107
115
  export class ClockQueryLimiter extends TimedQuerySemaphore {
108
- /** @param {number} rpm */
116
+ /** @param {number} rpm Requests per minute*/
109
117
  constructor(rpm){
110
118
  super(1, 60000 / rpm);
111
119
  }
112
120
  }
113
121
 
122
+ /** ClockQueryLimiter configured to stay within the start.gg API requests limit (60 per minute, so 1 second delay). You should use the StartGGDelayQueryLimiter instead*/
114
123
  export class StartGGClockQueryLimiter extends ClockQueryLimiter {
115
124
  constructor(){
116
125
  super(60);
117
126
  }
118
127
  }
119
128
 
129
+ /**TimedQuerySemaphore that allows x requests before waiting, and waits for one minute. */
120
130
  export class DelayQueryLimiter extends TimedQuerySemaphore {
121
131
  /** @param {number} rpm */
122
132
  constructor(rpm){
@@ -124,6 +134,7 @@ export class DelayQueryLimiter extends TimedQuerySemaphore {
124
134
  }
125
135
  }
126
136
 
137
+ /** ClockQueryLimiter configured to stay within the start.gg API requests limit (60 per minute)*/
127
138
  export class StartGGDelayQueryLimiter extends DelayQueryLimiter {
128
139
  constructor(){
129
140
  super(60)