startgg-helper
Version:
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)
237 lines (196 loc) • 9.8 kB
JavaScript
import { deep_get, deep_set } from './jsUtil.js';
import { TimedQuerySemaphore } from './queryLimiter.js'
function isConnection(val){
return val instanceof Object && val.nodes instanceof Array
}
export class Query {
#schema;
#maxTries;
/**
*
* @param {string} schema
* @param {number?} maxTries
*/
constructor (schema, maxTries = null){
this.#schema = schema;
this.#maxTries = maxTries;
}
/**
*
* @param {string} logName
* @param {{[varName: string]: value}} params
* @returns
*/
#getLog(logName, params){
if (!this.log) return null;
let log = this.log[logName];
if (log){
if (typeof log == "string"){
return log;
} else if (typeof log == "function"){
return log(params);
}
}
return null;
}
/**
*
* @param {GraphQLClient} client
* @param {{[varName: string]: value}} params
* @param {number} tries How many tries in are we
* @param {TimedQuerySemaphore} limiter
* @param {boolean} silentErrors
* @param {number} maxTries Overrides this.#maxTries
* @returns
*/
async #execute_(client, params, tries, limiter = null, silentErrors = false, maxTries = null){
maxTries = maxTries || this.#maxTries || 1
console.log((this.#getLog("query", params) || "Querying ...") + " Try " + (tries + 1));
try {
let data = await ( limiter ? limiter.execute(client, this.#schema, params) : client.request(this.#schema, params));
return data;
} catch (e) {
if (tries >= maxTries) {
console.error("Maximum number of tries reached. Throwing.", e);
throw e;
}
console.error((this.#getLog("error", params) || "Request failed.") + ` Retrying (try ${tries + 1}). Error : `, e);
return this.#execute_(client, params, tries + 1, limiter, silentErrors, maxTries);
}
}
/**
* Executes the query with given parameters and client
* @param {GraphQLClient} client
* @param {{[varName: string]: value}} params
* @param {TimedQuerySemaphore} limiter
* @param {boolean} silentErrors
* @param {number} maxTries Overrides the default maximum tries count for this query
* @returns
*/
async execute(client, params, limiter = null, silentErrors = false, maxTries = null){
return await this.#execute_(client, params, 0, limiter, silentErrors, maxTries);
}
static IWQModes = {
DONT: 0,
INLINE: 1,
DUPLICATE: 2,
OUT: 3
}
/**
* 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.
* @param {GraphQLClient} client
* @param {{[varName: string]: value}} params
* @param {string} connectionPathInQuery JSON path to the paginated collection that must aggregated in the query (JSON path : property names separated by dots)
* @param {TimedQuerySemaphore} limiter
* @param {{pageParamName?: string, perPageParamName?: string, perPage?: number, delay?: number, maxElements?: number, includeWholeQuery?: number}} config
* @param {boolean} silentErrors
* @param {number} maxTries
* @returns
*/
async executePaginated(client, params, connectionPathInQuery, limiter = null, config = {}, silentErrors = false, maxTries = null){
let result = [];
//delay = null, perPage = undefined, pageParamName = "page", perPageParamName = "perPage", silentErrors = false, maxTries = null
const pageParamName = config.pageParamName ?? "page";
const perPageParamName = config.perPageParamName ?? "perPage";
const perPage = config.perPage ?? params[perPageParamName];
const delay = config.delay;
const maxElements = config.maxElements ?? undefined; //eliminating null
let currentPage = 1;
params = Object.assign({}, params);
params[pageParamName] = currentPage;
params[perPageParamName] = perPage;
let data;
while (true){
if (result.length >= maxElements) break;
console.log("Querying page", params[pageParamName], `(${result.length} elements loaded)`);
data = await this.execute(client, params, limiter, silentErrors, maxTries);
if (!data) throw (this.#getLog("error", params) ?? "Request failed.") + "(in paginated execution, at page " + params[pageParamName] + ")";
let connection = deep_get(data, connectionPathInQuery);
if (!connection) {
console.warn(`The given path ${connectionPathInQuery} does not point to anything.`);
return null;
}
if (!isConnection(connection)) throw "The given path does not point to a connection type";
let localResult = connection.nodes;
if (connection.pageInfo && connection.pageInfo.totalPages){
let totalPages = connection.pageInfo.totalPages;
if (currentPage >= totalPages) {
result = result.concat(localResult);
break;
}
} else {
if (localResult.length < 1) break;
}
result = result.concat(localResult);
currentPage++;
params[pageParamName] = currentPage;
if (delay)
await new Promise(r => setTimeout(r, delay));
}
if (maxElements) result = result.slice(0, maxElements);
if (config.includeWholeQuery == Query.IWQModes.DUPLICATE || config.includeWholeQuery == Query.IWQModes.INLINE){
deep_set(data, connectionPathInQuery + ".nodes", result);
} else if (config.includeWholeQuery == Query.IWQModes.OUT){
deep_set(data, connectionPathInQuery + ".nodes", null);
}
if (config.includeWholeQuery == Query.IWQModes.DUPLICATE || config.includeWholeQuery == Query.IWQModes.OUT){
return [result, data]
} else if (config.includeWholeQuery == Query.IWQModes.INLINE){
return data;
}
return result;
}
/**
* 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.
* @param {GraphQLClient} client
* @param {{[varName: string]: value}} params
* @param {string} collectionPathInQuery JSON path to the paginated collection that must aggregated in the query (JSON path : property names separated by dots)
* @param {TimedQuerySemaphore} limiter
* @param {{pageParamName?: string, perPageParamName?: string, perPage?: number, delay?: number, maxElements?: number, includeWholeQuery?: number}} config
* @param {boolean} silentErrors
* @param {number} maxTries
* @returns
*/
async executePaginatedLegacy(client, params, collectionPathInQuery, limiter = null, config = {}, silentErrors = false, maxTries = null){
let result = [];
//delay = null, perPage = undefined, pageParamName = "page", perPageParamName = "perPage", silentErrors = false, maxTries = null
const pageParamName = config.pageParamName ?? "page";
const perPageParamName = config.perPageParamName ?? "perPage";
const perPage = config.perPage ?? params[perPageParamName];
const delay = config.delay;
const maxElements = config.maxElements ?? undefined; //eliminating null
params = Object.assign({}, params);
params[pageParamName] = 1;
params[perPageParamName] = perPage;
let data;
while (true){
if (result.length >= maxElements) break;
console.log("Querying page", params[pageParamName], `(${result.length} elements loaded)`);
data = await this.execute(client, params, limiter, silentErrors, maxTries);
if (!data) throw (this.#getLog("error", params) ?? "Request failed.") + "(in paginated execution, at page " + params[pageParamName] + ")";
let localResult = deep_get(data, collectionPathInQuery);
if (!localResult) {
console.warn(`The given path ${collectionPathInQuery} does not point to anything.`);
return null;
}
if (!localResult.push) throw "The given path does not point to an array."
if (localResult.length < 1) break;
result = result.concat(localResult);
params[pageParamName]++;
if (delay)
await new Promise(r => setTimeout(r, delay));
}
if (maxElements) result = result.slice(0, maxElements);
if (config.includeWholeQuery == Query.IWQModes.DUPLICATE || config.includeWholeQuery == Query.IWQModes.INLINE){
deep_set(data, collectionPathInQuery, result);
} else if (config.includeWholeQuery == Query.IWQModes.OUT){
deep_set(data, collectionPathInQuery, null);
}
if (config.includeWholeQuery == Query.IWQModes.DUPLICATE || config.includeWholeQuery == Query.IWQModes.OUT){
return [result, data]
} else if (config.includeWholeQuery == Query.IWQModes.INLINE){
return data;
}
return result;
}
}