Skip to content

Commit

Permalink
WIP Migrating CommonJS to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
lisandrobigi-onfleet committed Sep 3, 2024
1 parent cec126e commit f10fe2f
Show file tree
Hide file tree
Showing 22 changed files with 2,120 additions and 2,596 deletions.
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module.exports = require('./lib/onfleet');
import onfleet from './lib/onfleet.js';
export default onfleet;
148 changes: 69 additions & 79 deletions lib/Methods.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/* eslint-disable no-console */
const Bottleneck = require('bottleneck');
const fetch = require('node-fetch');
const constants = require('./constants');
const util = require('./util');
const {
import Bottleneck from 'bottleneck';
import fetch from 'node-fetch';
import * as constants from './constants.js';
import * as util from './util.js';
import {
HttpError,
PermissionError,
RateLimitError,
ServiceError,
} = require('./error');
} from './error.js';

// Create new rate limiter using defined constants
const limiter = new Bottleneck({
Expand All @@ -27,20 +27,18 @@ const reassignRate = (newRate) => {
}
};

const wait = (ms) => {
const wait = async (ms) => {
console.log('Waiting due to rate limiting');
// eslint-disable-next-line no-new
new Promise((resolve) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};

// On reservoir depletion, we wait 10000ms and reset the rate again (20 req/second limitation)
limiter.on('depleted', (empty) => {
limiter.on('depleted', async (empty) => {
if (!empty) {
wait(constants.LIMITER_WAIT_UPON_DEPLETION).then(() => {
reassignRate(constants.LIMITER_RESERVOIR);
});
await wait(constants.LIMITER_WAIT_UPON_DEPLETION);
reassignRate(constants.LIMITER_RESERVOIR);
}
});

Expand All @@ -50,7 +48,7 @@ limiter.on('depleted', (empty) => {
* @returns a promise containing the response from an HTTP request
*/

const Methods = (key, api, ...args) => {
const Methods = async (key, api, ...args) => {
const {
path,
altPath,
Expand All @@ -59,7 +57,7 @@ const Methods = (key, api, ...args) => {
deliveryManifestObject,
timeoutInMilliseconds,
} = key;
const operations = { method }['method']; // eslint-disable-line
const operations = method; // Instead of using ['method'], we directly assign `method`
let url = `${api.api.baseUrl}${path}`;
let body = '';
let hasBody = false;
Expand All @@ -68,25 +66,19 @@ const Methods = (key, api, ...args) => {
if (args.length === 0 && operations === 'GET' && altPath) {
url = `${api.api.baseUrl}${altPath}`;
}

// 1 or more arguments
if (args.length >= 1 && ['GET', 'DELETE', 'PUT'].includes(operations)) {
// If the 2nd argument is part of this array, this is a new endpoint
// This covers get(id, params) and insertTask(id, params)
if (['name', 'shortId', 'phone', 'workers', 'organizations', 'teams'].includes(args[1])) {
url = util.replaceWithEndpointAndParam(url, args[1], args[0]);
// If the 1st argument is a base 64 encoded ID, replaces URL path with ID
// This covers get(id), update(id), and deleteOne(id)
} else if (util.isBase64Encoded(args[0])) {
url = util.replaceWithId(url, args[0]);
// Else, use the alternate path
// This covers get() with no parameters passed in
} else {
url = `${api.api.baseUrl}${altPath}`;
}
// PUT Prep covering update(id, body)
// Second argument should be the body of the request, first arg is ID

if (operations === 'PUT') {
body = args[1]; // eslint-disable-line
body = args[1];
hasBody = true;
}
}
Expand All @@ -96,28 +88,29 @@ const Methods = (key, api, ...args) => {
}
// POST Prep - 3 different cases
if (operations === 'POST') {
if (util.isBase64Encoded(args[0])) { // forceComplete, clone, and autoDispatch (with ID)
if (util.isBase64Encoded(args[0])) {
url = util.replaceWithId(url, args[0]);
if (args[1]) { // forceComplete and autoDispatch (with ID, has body)
body = args[1]; // eslint-disable-line
if (args[1]) {
body = args[1];
hasBody = true;
}
} else { // create, batchCreate, matchMetadata, and autoAssign (no ID, has body)
body = args[0]; // eslint-disable-line
} else {
body = args[0];
hasBody = true;
}
}

// Query Params extension
if (queryParams) {
for (let element of args) { // eslint-disable-line
for (const element of args) {
if (util.isQueryParam(element)) {
url = util.appendQueryParameters(url, element);
}
}
}

// Reference https://docs.onfleet.com/reference/delivery-manifest
if (deliveryManifestObject && args && args.length > 0) {
let hasBody = false;
args.forEach((item) => {
if (item.hubId && item.workerId) {
body = {
Expand All @@ -127,7 +120,7 @@ const Methods = (key, api, ...args) => {
hasBody = true;
}
if (item.googleApiKey) {
api.api.headers["X-API-Key"] = "Google " + item.googleApiKey;
api.api.headers["X-API-Key"] = `Google ${item.googleApiKey}`;
}
if (item.startDate || item.endDate) {
const queryParams = {};
Expand All @@ -139,54 +132,51 @@ const Methods = (key, api, ...args) => {
}

// Send the HTTP request through the rate limiter
return limiter.schedule(() => fetch(url, {
method: operations,
headers: api.api.headers,
timeout: timeoutInMilliseconds,
body: hasBody ? JSON.stringify(body) : undefined,
}))
.then((res) => {
// For every request, we compare the reservoir with the remainding rate limit in the header
limiter.currentReservoir()
.then((reservoir) => {
if (reservoir < res.headers.get('x-ratelimit-remaining')) {
reassignRate(res.headers.get('x-ratelimit-remaining'));
}
});

if (res.ok) {
// Return status code for deletion as the API does, else, return the body of the response
if (operations === 'DELETE') {
return res.status;
}
return res.json().catch(() => res.status);
try {
const res = await limiter.schedule(() => fetch(url, {
method: operations,
headers: api.api.headers,
timeout: timeoutInMilliseconds,
body: hasBody ? JSON.stringify(body) : undefined,
}));

// For every request, we compare the reservoir with the remaining rate limit in the header
const reservoir = await limiter.currentReservoir();
const rateLimitRemaining = res.headers.get('x-ratelimit-remaining');
if (reservoir < rateLimitRemaining) {
reassignRate(rateLimitRemaining);
}

if (res.ok) {
if (operations === 'DELETE') {
return res.status;
}
return res.json().catch(() => res.status);
}

return res.json().then((error) => {
// Throws custom error according to errorCode
const errorCode = error.message.error;
const errorInfo = [
error.message.message,
errorCode,
error.message.cause,
error.message.request,
];
if (errorCode === 2300) {
throw new RateLimitError(errorInfo[0], errorInfo[1], errorInfo[2], errorInfo[3]);
} else if (errorCode <= 1108 && errorCode >= 1100) {
throw new PermissionError(errorInfo[0], errorInfo[1], errorInfo[2], errorInfo[3]);
} else if (errorCode >= 2500) {
throw new ServiceError(errorInfo[0], errorInfo[1], errorInfo[2], errorInfo[3]);
} else if (errorCode === 2218) { // Precondition error for Auto-Dispatch
throw new ServiceError(errorInfo[0], errorInfo[1], errorInfo[2], errorInfo[3]);
}
// All others, throw general HTTP error
throw new HttpError(errorInfo[0], errorInfo[1], errorInfo[2], errorInfo[3]);
});
})
.catch((error) => {
throw (error);
});
const error = await res.json();
const errorCode = error.message.error;
const errorInfo = [
error.message.message,
errorCode,
error.message.cause,
error.message.request,
];

if (errorCode === 2300) {
throw new RateLimitError(...errorInfo);
} else if (errorCode >= 1100 && errorCode <= 1108) {
throw new PermissionError(...errorInfo);
} else if (errorCode >= 2500) {
throw new ServiceError(...errorInfo);
} else if (errorCode === 2218) { // Precondition error for Auto-Dispatch
throw new ServiceError(...errorInfo);
}
throw new HttpError(...errorInfo);

} catch (error) {
throw error;
}
};

module.exports = Methods;
export default Methods;
27 changes: 14 additions & 13 deletions lib/Resource.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const methods = require('./Methods');
import methods from './Methods.js';

/**
* @desc Resource class initiates all CRUD operations
*/

class Resource {
constructor(api) {
this.api = api;
Expand All @@ -12,27 +11,29 @@ class Resource {

defineTimeout(timeout) {
/**
* @desc defines the timeout value of the endpoint
* if none is specified, uses the default API timeout
* @desc Defines the timeout value of the endpoint
* If none is specified, uses the default API timeout
*/
const preferredTimeout = timeout || this.api.api.timeout;
this.timeoutInMilliseconds = preferredTimeout;
}

endpoints(params) {
/**
* @desc initiates all the methods set in each resource configuration
* @desc Initiates all the methods set in each resource configuration
* @param params is the configuration parsed in from each API endpoint
*/
for (let key in params) { // eslint-disable-line
this[key] = params[key];
this[key].timeoutInMilliseconds = this.timeoutInMilliseconds;
// Create functions for each method
this[key] = (...args) => { // eslint-disable-line
return methods(params[key], this.api, ...args);
};
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
this[key] = params[key];
this[key].timeoutInMilliseconds = this.timeoutInMilliseconds;
// Create functions for each method
this[key] = (...args) => {
return methods(params[key], this.api, ...args);
};
}
}
}
}

module.exports = Resource;
export default Resource;
12 changes: 5 additions & 7 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const constants = {
LIMITER_RESERVOIR: 20,
LIMITER_WAIT_UPON_DEPLETION: 10000,
LIMITER_MAX_CONCURRENT: 1,
LIMITER_MIN_TIME: 50,
};
// lib/constants.js

module.exports = constants;
export const LIMITER_MAX_CONCURRENT = 1;
export const LIMITER_MIN_TIME = 50;
export const LIMITER_WAIT_UPON_DEPLETION = 10000;
export const LIMITER_RESERVOIR = 20;
14 changes: 8 additions & 6 deletions lib/error.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable max-classes-per-file */
// Define custom error classes

class ValidationError extends Error {
constructor(message) {
super(message);
Expand All @@ -9,7 +10,7 @@ class ValidationError extends Error {

class PermissionError extends Error {
constructor(message, status, cause, request) {
super(message, status, cause, request);
super(message);
this.name = 'PermissionError';
this.message = message;
this.status = status;
Expand All @@ -20,7 +21,7 @@ class PermissionError extends Error {

class HttpError extends Error {
constructor(message, status, cause, request) {
super(message, status, cause, request);
super(message);
this.name = 'HttpError';
this.message = message;
this.status = status;
Expand All @@ -31,7 +32,7 @@ class HttpError extends Error {

class RateLimitError extends Error {
constructor(message, status, cause, request) {
super(message, status, cause, request);
super(message);
this.name = 'RateLimitError';
this.message = message;
this.status = status;
Expand All @@ -42,7 +43,7 @@ class RateLimitError extends Error {

class ServiceError extends Error {
constructor(message, status, cause, request) {
super(message, status, cause, request);
super(message);
this.name = 'ServiceError';
this.message = message;
this.status = status;
Expand All @@ -51,7 +52,8 @@ class ServiceError extends Error {
}
}

module.exports = {
// Export the custom error classes
export {
ValidationError,
PermissionError,
HttpError,
Expand Down
Loading

0 comments on commit f10fe2f

Please sign in to comment.