@bss-sbc/shopify-api-fetcher
Advanced tools
Comparing version 2.0.1 to 2.0.3
@@ -32,36 +32,95 @@ "use strict"; | ||
const token = process.env.TOKEN || ''; | ||
const version = process.env.API_VERSION || '2022-04'; | ||
describe('GraphQL API Test', () => { | ||
test('intialize', () => { | ||
(0, graphql_1.setConfig)({ | ||
version, | ||
}); | ||
expect(graphql_1.QueueManager.initialize(domain)).toBe(1); | ||
test('call api ok', async () => { | ||
(0, graphql_1.setConfig)({ version: '2022-10' }); | ||
const query = ` | ||
query { | ||
customers(first: 250) { | ||
edges { | ||
node { | ||
id | ||
displayName | ||
firstName | ||
lastName | ||
tags | ||
taxExempt | ||
addresses { | ||
address1 | ||
address2 | ||
city | ||
company | ||
country | ||
countryCode | ||
firstName | ||
lastName | ||
phone | ||
province | ||
provinceCode | ||
zip | ||
phone | ||
} | ||
} | ||
cursor | ||
} | ||
pageInfo { | ||
hasNextPage | ||
hasPreviousPage | ||
endCursor | ||
startCursor | ||
} | ||
} | ||
} | ||
`; | ||
const json = await (0, graphql_1.safeFetch)(domain, token, { query }); | ||
expect(json.data.customers).toBeTruthy(); | ||
}); | ||
test('call api', (done) => { | ||
(0, graphql_1.safeFetch)(domain, token, { | ||
query: ` | ||
query queryShop { | ||
shop { | ||
name | ||
contactEmail | ||
myshopifyDomain | ||
ianaTimezone | ||
currencyCode | ||
currencyFormats { | ||
moneyFormat | ||
test('call api failed', async () => { | ||
const query = ` | ||
query { | ||
customers(first: ) { | ||
edges { | ||
node { | ||
id | ||
displayName | ||
firstName | ||
lastName | ||
tags | ||
taxExempt | ||
addresses { | ||
address1 | ||
address2 | ||
city | ||
company | ||
country | ||
countryCode | ||
firstName | ||
lastName | ||
phone | ||
province | ||
provinceCode | ||
zip | ||
phone | ||
} | ||
} | ||
cursor | ||
} | ||
pageInfo { | ||
hasNextPage | ||
hasPreviousPage | ||
endCursor | ||
startCursor | ||
} | ||
} | ||
`, | ||
}) | ||
.then((data) => { | ||
expect(data.data.shop.myshopifyDomain).toBe(domain); | ||
done(); | ||
}) | ||
.catch((err) => { | ||
done(err); | ||
}); | ||
} | ||
`; | ||
try { | ||
await (0, graphql_1.safeFetch)(domain, token, { query, variables: { 'x': 'x' } }); | ||
expect(true).toBe(true); | ||
} | ||
catch (err) { | ||
expect(err instanceof graphql_1.GraphqlError).toBe(true); | ||
} | ||
}); | ||
}); |
@@ -1,9 +0,3 @@ | ||
import fetch, { RequestInfo, RequestInit } from 'node-fetch'; | ||
declare class Queue { | ||
frontIndex: number; | ||
backIndex: number; | ||
constructor(); | ||
enqueue(): number; | ||
dequeue(): boolean; | ||
peek(): number; | ||
export declare class GraphqlError extends Error { | ||
constructor(message?: string, options?: ErrorOptions); | ||
} | ||
@@ -15,10 +9,2 @@ type CommonConfig = { | ||
}; | ||
type Metric = { | ||
total: number; | ||
executions: number; | ||
processing: number; | ||
success: number; | ||
errors: number; | ||
throttles: number; | ||
}; | ||
type Body = { | ||
@@ -28,23 +14,4 @@ query: string; | ||
}; | ||
declare class ShopifyGraphQL { | ||
fetch: typeof fetch; | ||
endpoint: RequestInfo; | ||
queue: Queue; | ||
_metrics: Metric; | ||
constructor(endpoint: RequestInfo); | ||
request(params: { | ||
domain: string; | ||
token: string; | ||
body: Body; | ||
}): Promise<unknown>; | ||
_executeRequest(params: RequestInit): Promise<unknown>; | ||
} | ||
export declare class QueueManager { | ||
private static queues; | ||
static get(domain: string): ShopifyGraphQL; | ||
static initialize(domain: string): number; | ||
static clear(domain: string): void; | ||
} | ||
export declare function safeFetch(domain: string, token: string, body: Body): Promise<unknown>; | ||
export declare function setConfig(cfg: Partial<CommonConfig>): void; | ||
export {}; |
@@ -6,5 +6,12 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.setConfig = exports.safeFetch = exports.QueueManager = void 0; | ||
exports.setConfig = exports.safeFetch = exports.GraphqlError = void 0; | ||
const node_fetch_1 = __importDefault(require("node-fetch")); | ||
const utils_1 = require("./utils"); | ||
class GraphqlError extends Error { | ||
constructor(message, options) { | ||
super(message, options); | ||
this.name = ` GraphqlError`; | ||
} | ||
} | ||
exports.GraphqlError = GraphqlError; | ||
class Queue { | ||
@@ -52,2 +59,3 @@ frontIndex; | ||
_metrics; | ||
lastActive; | ||
constructor(endpoint) { | ||
@@ -57,2 +65,3 @@ this.fetch = node_fetch_1.default; | ||
this.queue = new Queue(); | ||
this.lastActive = new Date(); | ||
if (!this.endpoint) { | ||
@@ -71,2 +80,3 @@ throw new Error('Missing Shop URL'); | ||
request(params) { | ||
this.lastActive = new Date(); | ||
return new Promise(async (resolve, reject) => { | ||
@@ -113,3 +123,3 @@ this._metrics.total += 1; | ||
this._metrics.errors += 1; | ||
return reject(new Error('', { | ||
return reject(new GraphqlError('', { | ||
cause: { | ||
@@ -139,3 +149,3 @@ status: shopifyResult.status, | ||
this._metrics.processing -= 1; | ||
return reject(new Error('', { | ||
return reject(new GraphqlError('', { | ||
cause: { | ||
@@ -170,3 +180,3 @@ status: isThrottled ? 'throttled' : shopifyResult.status, | ||
this._metrics.errors += 1; | ||
return reject(new Error('', { | ||
return reject(new GraphqlError('', { | ||
cause: { | ||
@@ -185,6 +195,5 @@ status: shopifyResult.status, | ||
.catch((shopifyError) => { | ||
console.log(shopifyError); | ||
this._metrics.processing -= 1; | ||
this._metrics.errors += 1; | ||
return reject(new Error('', { cause: shopifyError })); | ||
return reject(new GraphqlError('', { cause: shopifyError })); | ||
}); | ||
@@ -207,3 +216,3 @@ }); | ||
QueueManager.queues.set(domain, shopifyGraphQL); | ||
return QueueManager.queues.size; | ||
setInterval; | ||
} | ||
@@ -214,3 +223,2 @@ static clear(domain) { | ||
} | ||
exports.QueueManager = QueueManager; | ||
async function safeFetch(domain, token, body) { | ||
@@ -221,6 +229,20 @@ return QueueManager.get(domain).request({ domain, token, body }); | ||
function setConfig(cfg) { | ||
globalCfg.version = cfg.version ?? '2022-04'; | ||
if (!cfg.version || cfg.version < '2022-10') { | ||
throw new Error('Invalid version'); | ||
} | ||
globalCfg.version = cfg.version; | ||
globalCfg.retryThrottles = cfg.retryThrottles !== undefined ? cfg.retryThrottles : true; | ||
globalCfg.maxConcurrentRequests = cfg.maxConcurrentRequests !== undefined ? cfg.maxConcurrentRequests : 50; | ||
if (process.env.NODE_ENV === 'production') { | ||
setInterval(() => { | ||
for (const [key, value] of QueueManager.queues.entries()) { | ||
const now = new Date(); | ||
now.setDate(now.getDate() - 1); | ||
if (value.lastActive < now) { | ||
QueueManager.clear(key); | ||
} | ||
} | ||
}, 24 * 60 * 60); | ||
} | ||
} | ||
exports.setConfig = setConfig; |
{ | ||
"name": "@bss-sbc/shopify-api-fetcher", | ||
"version": "2.0.1", | ||
"version": "2.0.3", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -11,14 +11,6 @@ # Shopify API Fetcher | ||
* `BucketManager`: Manage Token bucket rate limiter of active store | ||
* `Rest.safeFetch: (domain: string, url: RequestInfo, params?: RequestInit) => Promise<Response>`: use this instead of normal `fetch`, with additional parameter `domain`, which specify a store. | ||
* `initializeAll: (activeStores: Shop[]) => number;`: Initialize rate limiter for all given stores | ||
* `GraphQL.safeFetch: (domain: string, token: string, { query: string, variables?: Object }) => Promise<Unknown>`: use this instead of normal `fetch`, with additional parameter `domain`, which specify a store. | ||
* `initializeOne: (domain: string) => TokenBucketRateLimiter`: Initialize rate limiter for specific domain | ||
* `get: (domain: string) => TokenBucketRateLimiter`: Get initialized rate limiter of a specific domain | ||
* `clear: (domain: string) => void`: Clear rate limiter of a specific domain, used when app is uninstalled | ||
* `safeFetch: (domain: string, url: RequestInfo, params?: RequestInit) => Promise<Response>`: use this instead of normal `fetch`, with additional parameter `domain`, which specify a store. | ||
### Example | ||
@@ -67,12 +59,5 @@ | ||
### Roadmap | ||
Because of numbers of `services`, it is difficult to listen to `app/uninstalled` webhook event. So, auto clear bucket is necessary | ||
- [x] REST | ||
- [ ] GraphQL | ||
- [ ] Time-to-live option | ||
- [ ] (Updating) | ||
- [ ] Time-to-live REST | ||
- [x] GraphQL | ||
- [x] Time-to-live GraphQL |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
26248
630
62