feathers-shippo
Version:
A Feathers JS adapter for the Shippo API
280 lines (279 loc) • 10.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShippoService = exports.shippo = exports.axiosOpts = exports.shippoLimiters = exports.shippoLimiter = exports.calcMinTime = void 0;
const errors = __importStar(require("@feathersjs/errors"));
const bottleneck_1 = __importDefault(require("bottleneck"));
const axios_1 = __importDefault(require("axios"));
const SHIPPO_API_URL = 'https://api.goshippo.com';
[];
const calcMinTime = (perMinute) => 60000 / perMinute;
exports.calcMinTime = calcMinTime;
// https://goshippo.com/docs/rate-limits/
const liveMinTimes = {
create: (0, exports.calcMinTime)(500),
update: (0, exports.calcMinTime)(500),
get: (0, exports.calcMinTime)(4000),
find: (0, exports.calcMinTime)(50),
remove: (0, exports.calcMinTime)(500) // Note most services don't actually have remove
};
const testMinTimes = {
create: (0, exports.calcMinTime)(50),
update: (0, exports.calcMinTime)(50),
get: (0, exports.calcMinTime)(400),
find: (0, exports.calcMinTime)(5),
remove: (0, exports.calcMinTime)(50) // Note most services don't actually have remove
};
const shippoLimiter = (method, minTimes) => {
const limiter = new bottleneck_1.default({ minTime: minTimes[method] });
// TODO: Inspect the error for a timeoute/expiry to retry after
limiter.on('failed', (error, jobInfo) => __awaiter(void 0, void 0, void 0, function* () {
if (error.code === 429 && jobInfo.retryCount === 0) {
// Retry once after 200ms
return 200;
}
}));
return limiter;
};
exports.shippoLimiter = shippoLimiter;
const shippoLimiters = (token) => {
if (!token) {
throw new errors.GeneralError('options.token is required a Shippo API Token for new ShippoService()');
}
const minTimes = token.startsWith('shippo_live')
? liveMinTimes
: testMinTimes;
return {
create: (0, exports.shippoLimiter)('create', minTimes),
update: (0, exports.shippoLimiter)('update', minTimes),
get: (0, exports.shippoLimiter)('get', minTimes),
find: (0, exports.shippoLimiter)('find', minTimes),
remove: (0, exports.shippoLimiter)('remove', minTimes)
};
};
exports.shippoLimiters = shippoLimiters;
const axiosOpts = (params) => {
return {
params: params === null || params === void 0 ? void 0 : params.query
};
};
exports.axiosOpts = axiosOpts;
const shippoResource = (service) => {
const { path } = service.options;
return {
create: (data, params) => {
return service.schedule('create', () => __awaiter(void 0, void 0, void 0, function* () {
return service.shippo.post(path, data, (0, exports.axiosOpts)(params));
}));
},
update: (id, data, params) => {
return service.schedule('update', () => {
if (!id) {
throw new errors.BadRequest('ID is required');
}
return service.shippo.put(`${path}/${id}`, data, (0, exports.axiosOpts)(params));
});
},
get: (id, params) => {
return service.schedule('get', () => {
if (!id) {
throw new errors.BadRequest('ID is required');
}
return service.shippo.get(`${path}/${id}`, (0, exports.axiosOpts)(params));
});
},
find: (params) => {
return service.schedule('find', () => __awaiter(void 0, void 0, void 0, function* () {
return service.shippo.get(path, (0, exports.axiosOpts)(params));
}));
},
remove: (id, params) => {
return service.schedule('remove', () => {
return service.shippo.delete(`${path}/${id}`, (0, exports.axiosOpts)(params));
});
}
};
};
const shippo = (token) => {
return axios_1.default.create({
baseURL: SHIPPO_API_URL,
headers: {
Authorization: `ShippoToken ${token}`,
'Accept-Encoding': null
}
});
};
exports.shippo = shippo;
class ShippoService {
constructor(options, app) {
this.options = options;
this.app = app;
const { token, path } = options;
if (!token) {
throw new errors.GeneralError('options.token is required a Shippo API Token for new ShippoService()');
}
if (!path) {
throw new errors.GeneralError(`${path} is invalid options.path for new ShippoService()`);
}
this.shippo = (0, exports.shippo)(token);
this.resource = shippoResource(this);
}
handleError(error, params) {
if (params) {
params.shippoError = error;
}
if (!error.response) {
throw error;
}
const FeathersError = errors[error.response.status];
if (FeathersError) {
throw new FeathersError(error.message, error.response.data);
}
throw new errors.BadRequest(error.message || error, error);
}
handleMethod(method) {
const { methods, path } = this.options;
if (!methods.includes(method)) {
throw new errors.NotImplemented(`"${method}" not implemented on "${path}" ShippoService`);
}
}
handleResult(result, params) {
if (params) {
params.shippoResult = result;
}
if (result.data.results) {
result.data.data = result.data.results;
delete result.data.results;
}
if (result.data.count) {
result.data.total = result.data.count;
delete result.data.count;
}
return result.data;
}
schedule(method, fn) {
const { limiters } = this.options;
if (limiters && limiters[method]) {
return limiters[method].schedule(fn);
}
return fn();
}
_create(data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('create');
return this.resource
.create(data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
create(data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._create(data, params);
});
}
_get(id, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('get');
return this.resource
.get(id, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
get(id, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._get(id, params);
});
}
_find(params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('find');
return this.resource
.find(params)
.then(this.handleResult)
.catch(this.handleError);
});
}
find(params) {
return __awaiter(this, void 0, void 0, function* () {
return this._find(params);
});
}
_update(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('update');
return this.resource
.update(id, data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
update(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._update(id, data, params);
});
}
_patch(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('patch');
return this._update(id, data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
patch(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._patch(id, data, params);
});
}
_remove(id, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('remove');
return this.resource
.remove(id, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
remove(id, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._remove(id, params);
});
}
}
exports.ShippoService = ShippoService;