Socket
Socket
Sign inDemoInstall

shopify-api-node

Package Overview
Dependencies
Maintainers
2
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shopify-api-node - npm Package Compare versions

Comparing version 3.9.1 to 3.10.0

243

index.js

@@ -12,2 +12,17 @@ 'use strict';

const retryableErrorCodes = new Set([
'ETIMEDOUT',
'ECONNRESET',
'EADDRINUSE',
'ECONNREFUSED',
'EPIPE',
'ENOTFOUND',
'ENETUNREACH',
'EAI_AGAIN'
]);
const retryableStatusCodes = new Set([
408, 413, 429, 500, 502, 503, 504, 521, 522, 524
]);
/**

@@ -29,2 +44,4 @@ * Creates a Shopify instance.

* JSON
* @param {Number} [options.maxRetries] Maximum number of automatic request
* retries
* @constructor

@@ -39,3 +56,4 @@ * @public

(!options.accessToken && (!options.apiKey || !options.password)) ||
(options.accessToken && (options.apiKey || options.password))
(options.accessToken && (options.apiKey || options.password)) ||
(options.autoLimit && options.maxRetries)
) {

@@ -50,2 +68,3 @@ throw new Error('Missing or invalid options');

timeout: 60000,
maxRetries: 0,
...options

@@ -141,4 +160,20 @@ };

responseType: 'json',
retry: 0,
method
retry:
this.options.maxRetries > 0
? {
limit: this.options.maxRetries,
// Don't clamp Shopify `Retry-After` header values too low.
maxRetryAfter: Infinity,
calculateDelay
}
: 0,
method,
hooks: {
afterResponse: [
(res) => {
this.updateLimits(res.headers['x-shopify-shop-api-call-limit']);
return res;
}
]
}
};

@@ -150,49 +185,38 @@

return got(uri, options).then(
(res) => {
const body = res.body;
return got(uri, options).then((res) => {
const body = res.body;
this.updateLimits(res.headers['x-shopify-shop-api-call-limit']);
if (res.statusCode === 202 && res.headers['location']) {
const retryAfter = res.headers['retry-after'] * 1000 || 0;
const { pathname, search } = url.parse(res.headers['location']);
if (res.statusCode === 202 && res.headers['location']) {
const retryAfter = res.headers['retry-after'] * 1000 || 0;
const { pathname, search } = url.parse(res.headers['location']);
return delay(retryAfter).then(() => {
const uri = { pathname, ...this.baseUrl };
return delay(retryAfter).then(() => {
const uri = { pathname, ...this.baseUrl };
if (search) uri.search = search;
if (search) uri.search = search;
return this.request(uri, 'GET', key);
});
}
return this.request(uri, 'GET', key);
const data = key ? body[key] : body || {};
if (res.headers.link) {
const link = parseLinkHeader(res.headers.link);
if (link.next) {
Object.defineProperties(data, {
nextPageParameters: { value: link.next.query }
});
}
const data = key ? body[key] : body || {};
if (res.headers.link) {
const link = parseLinkHeader(res.headers.link);
if (link.next) {
Object.defineProperties(data, {
nextPageParameters: { value: link.next.query }
});
}
if (link.previous) {
Object.defineProperties(data, {
previousPageParameters: { value: link.previous.query }
});
}
if (link.previous) {
Object.defineProperties(data, {
previousPageParameters: { value: link.previous.query }
});
}
}
return data;
},
(err) => {
this.updateLimits(
err.response && err.response.headers['x-shopify-shop-api-call-limit']
);
return Promise.reject(err);
}
);
return data;
});
};

@@ -252,26 +276,35 @@

responseType: 'json',
retry: 0,
method: 'POST',
body: json ? this.options.stringifyJson({ query: data, variables }) : data
};
body: json ? this.options.stringifyJson({ query: data, variables }) : data,
retry:
this.options.maxRetries > 0
? {
limit: this.options.maxRetries,
// Don't clamp Shopify `Retry-After` header values too low.
maxRetryAfter: Infinity,
calculateDelay
}
: 0,
hooks: {
afterResponse: [
(res) => {
if (res.body) {
if (res.body.extensions && res.body.extensions.cost) {
this.updateGraphqlLimits(res.body.extensions.cost);
}
return got(uri, options).then((res) => {
if (res.body.extensions && res.body.extensions.cost) {
this.updateGraphqlLimits(res.body.extensions.cost);
}
if (res.body.errors) {
// Make Got consider this response errored and retry if needed.
throw new Error(res.body.errors[0].message);
}
}
if (res.body.errors) {
const first = res.body.errors[0];
const err = new Error(first.message);
err.locations = first.locations;
err.path = first.path;
err.extensions = first.extensions;
err.response = res;
throw err;
return res;
}
],
beforeError: [decorateError]
}
};
return res.body.data || {};
});
return got(uri, options).then(responseData);
};

@@ -282,2 +315,34 @@

/**
* Got `calculateDelay` hook function passed to decide how long to wait before
* retrying.
*
* @param {Object} retryObject Got's input for the retry logic
* @return {Number} The delay
* @private
*/
function calculateDelay(retryObject) {
return maybeRetryMS(retryObject.error) || retryObject.computedValue;
}
/**
* Decorates an `Error` object with details from GraphQL errors in the response
* body.
*
* @param {Error} error The error to decorate
* @return {Error} The decorated error
* @private
*/
function decorateError(error) {
if (error.response && error.response.body.errors) {
const first = error.response.body.errors[0];
error.locations = first.locations;
error.path = first.path;
error.extensions = first.extensions;
}
return error;
}
/**
* Returns a promise that resolves after a given amount of time.

@@ -294,2 +359,51 @@ *

/**
* Given an error from Got, see if Shopify told us how long to wait before
* retrying.
*
* @param {Object} error Error object from Got call
* @return {(Number|null)} The duration in ms, or `null`
* @private
**/
function maybeRetryMS(error) {
// For simplicity, retry network connectivity issues after a hardcoded 1s.
if (retryableErrorCodes.has(error.code)) {
return 1000;
}
const response = error.response;
if (response.headers && response.headers['retry-after']) {
return response.headers['retry-after'] * 1000 || null;
}
if (retryableStatusCodes.has(response.statusCode)) {
// Arbitrary 2 seconds, in case we get a 429 without a `Retry-After`
// response header, or 4xx/5xx series error that matches the Got retry
// defaults.
return 2 * 1000;
}
// Detect GraphQL request throttling.
if (response.body && typeof response.body === 'object') {
const body = response.body;
if (
body.errors &&
body.errors[0].extensions &&
body.errors[0].extensions.code == 'THROTTLED'
) {
const costData = body.extensions.cost;
return (
((costData.requestedQueryCost -
costData.throttleStatus.currentlyAvailable) /
costData.throttleStatus.restoreRate) *
1000
);
}
}
return null;
}
/**
* Parses the `Link` header into an object.

@@ -325,2 +439,13 @@ *

/**
* Returns the data of a GraphQL response object.
*
* @param {Response} res Got response object
* @return {Object} The data
* @private
*/
function responseData(res) {
return res.body.data;
}
module.exports = Shopify;
{
"name": "shopify-api-node",
"version": "3.9.1",
"version": "3.10.0",
"description": "Shopify API bindings for Node.js",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -55,3 +55,3 @@ # Shopify API Node.js

set to `true` requests are limited as specified in the above example. Defaults
to `false`.
to `false`. Mutually exclusive with the `maxRetries` option.
- `parseJson` - Optional - The function used to parse JSON. The function is

@@ -70,2 +70,9 @@ passed a single argument. This option allows the use of a custom JSON parser

to `60000`, or 1 minute.
- `maxRetries` - Optional - The number of times to attempt to make the request
to Shopify before giving up. Defaults to `0`, which means no automatic
retries. If set to a value greater than `0`, `shopify-api-node` will make up
to that many retries. `shopify-api-node` will respect the `Retry-After` header
for requests to the REST API, and the throttled cost data for requests to the
GraphQL API, and retry the request after that time has elapsed. Mutually
exclusive with the `autoLimit` option.

@@ -261,2 +268,31 @@ #### Return value

## Shopify rate limit avoidance
`shopify-api-node` has two optional mechanisms for avoiding requests failing
with `429 Rate Limit Exceeded` errors from Shopify.
The `autoLimit` option implements a client side leaky bucket algorithm for
delaying requests until Shopify is likely to accept them. When `autoLimit` is
on, each `Shopify` instance will track how many requests have been made, and
delay sending subsequent requests if the rate limit has been exceeded.
`autoLimit` is very efficient because it almost entirely avoids sending requests
which will return 429 errors, but, it does not coordinate between multiple
`Shopify` instances or across multiple processes. If you're using
`shopify-api-node` in many different processes, `autoLimit` will not correctly
avoid 429 errors.
The `maxRetries` option implements a retry based strategy for getting requests
to Shopify, where when a 429 error occurs, the request is automatically retried
after waiting. Shopify usually replies with a `Retry-After` header indicating to
the client when the rate limit is available, and so `shopify-api-node` will wait
that long before retrying. If you are using `shopify-api-node` in many different
processes, they will all be competing to use the same rate limit shopify
enforces, so there is no guarantee that retrying after the `Retry-After` header
delay will work. It is recommended to set `maxRetries` to a high value like `10`
if you are making many concurrent requests in many processes to ensure each
request is retried for long enough to succeed.
`autoLimit` and `maxRetries` can't be used simultaneously. Both are off by
default.
## Available resources and methods

@@ -458,3 +494,3 @@

- `update(id, params)`
- [giftCardAdjustment](https://help.shopify.com/en/api/reference/plus/gift_card_adjustment)
- [giftCardAdjustment](https://shopify.dev/api/admin-rest/2022-04/resources/gift-card-adjustment)
- `create(giftCardId, params)`

@@ -554,3 +590,3 @@ - `get(giftCardId, id)`

- `productIds([params])`
- [productResourceFeedback](https://help.shopify.com/en/api/reference/sales-channels/productresourcefeedback)
- [productResourceFeedback](https://shopify.dev/api/admin-rest/2022-04/resources/product-resourcefeedback)
- `create(productId[, params])`

@@ -651,4 +687,4 @@ - `list(productId)`

where `params` is a plain JavaScript object. See
https://help.shopify.com/api/reference?ref=microapps for parameters details.
where `params` is a plain JavaScript object. See the [Rest Admin API
reference][reading-api-docs] for parameters details.

@@ -685,4 +721,2 @@ ## GraphQL

- [Becoming a Shopify App Developer][becoming-a-shopify-app-developer]
- [Checking out the roots][checking-out-the-roots]
- [Talking To Other Masters][talking-to-other-masters]

@@ -731,17 +765,11 @@ - [Reading API Docs][reading-api-docs]

[generate-private-app-credentials]:
https://help.shopify.com/api/guides/api-credentials#generate-private-app-credentials?ref=microapps
[oauth]: https://help.shopify.com/api/guides/authentication/oauth?ref=microapps
https://shopify.dev/apps/auth/basic-http#step-2-generate-api-credentials
[oauth]: https://shopify.dev/apps/auth/oauth
[shopify-token]: https://github.com/lpinca/shopify-token
[api-call-limit]:
https://help.shopify.com/api/guides/api-call-limit/?ref=microapps
[api-versioning]: https://help.shopify.com/en/api/versioning
[becoming-a-shopify-app-developer]:
https://app.shopify.com/services/partners/signup?ref=microapps
[checking-out-the-roots]: https://help.shopify.com/api/guides?ref=microapps
[talking-to-other-masters]:
https://ecommerce.shopify.com/c/shopify-apps?ref=microapps
[reading-api-docs]: https://help.shopify.com/api/reference/?ref=microapps
[api-call-limit]: https://shopify.dev/api/usage/rate-limits
[api-versioning]: https://shopify.dev/api/usage/versioning
[talking-to-other-masters]: https://community.shopify.com/
[reading-api-docs]: https://shopify.dev/api/admin-rest
[learning-from-others]: https://stackoverflow.com/questions/tagged/shopify
[paginated-rest-results]:
https://help.shopify.com/en/api/guides/paginated-rest-results
[paginated-rest-results]: https://shopify.dev/api/usage/pagination-rest
[polaris]: https://polaris.shopify.com/?ref=microapps

@@ -748,0 +776,0 @@ [microapps]:

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc