scryfall-sdk
Version:
A Node.js SDK for https://scryfall.com/docs/api written in Typescript.
156 lines (155 loc) • 7.44 kB
JavaScript
;
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.minimumRequestTimeout = exports.defaultRequestTimeout = void 0;
const IScry_1 = require("../IScry");
let axios;
if (typeof fetch === "undefined") {
try {
axios = require("axios").default;
}
catch (_a) {
throw new Error("[scryfall-sdk] If the global `fetch` function is undefined (any node.js version older than v18), the axios peerDependency is required.");
}
}
// the api requests 50-100 ms between calls, we go on the generous side and never wait less than 100 ms between calls
exports.defaultRequestTimeout = 100;
exports.minimumRequestTimeout = 50;
let lastQuery = 0;
function sleep(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class MagicQuerier {
query(apiPath, query, post) {
return __awaiter(this, void 0, void 0, function* () {
if (Array.isArray(apiPath))
apiPath = apiPath.join("/");
let lastError;
let result;
let retries;
for (retries = 0; retries < MagicQuerier.retry.attempts; retries++) {
({ result, lastError } = yield this.tryQuery(`${apiPath}`, query, post));
if (result || (!this.canRetry(lastError) && !MagicQuerier.retry.forced))
break;
yield sleep(MagicQuerier.retry.timeout);
}
if (!result) {
lastError !== null && lastError !== void 0 ? lastError : (lastError = new Error("No data"));
lastError.attempts = retries;
throw lastError;
}
return result;
});
}
queryPage(emitter, apiPath, query, page) {
var _a, _b;
if (page === void 0) { page = (_a = query === null || query === void 0 ? void 0 : query.page) !== null && _a !== void 0 ? _a : 1; }
return __awaiter(this, void 0, void 0, function* () {
let error;
const results = yield this.query(apiPath, Object.assign(Object.assign({}, query), { page }))
.catch(err => error = err);
const data = (_b = results === null || results === void 0 ? void 0 : results.data) !== null && _b !== void 0 ? _b : [];
if ((results === null || results === void 0 ? void 0 : results.object) !== "list" && error === undefined) {
emitter.emit("error", new Error("Result object is not a list"));
return;
}
for (const card of data) {
if (emitter.cancelled)
break;
emitter.emit("data", card);
}
if ((results === null || results === void 0 ? void 0 : results.has_more) && data.length !== 0) { // check if there was no data to workaround scryfall being buggy and returning true for invalid pages
if (!emitter.cancelled) {
if (emitter.willCancelAfterPage)
emitter.cancel();
else
return this.queryPage(emitter, apiPath, query, page + 1)
.catch(err => { emitter.emit("error", err); });
}
}
if (!emitter.cancelled)
emitter.emit("end");
emitter.emit("done");
});
}
tryQuery(apiPath, query, post) {
return __awaiter(this, void 0, void 0, function* () {
const now = Date.now();
const timeSinceLastQuery = now - lastQuery;
if (timeSinceLastQuery >= MagicQuerier.timeout) {
lastQuery = now;
}
else {
const timeUntilNextQuery = MagicQuerier.timeout - timeSinceLastQuery;
lastQuery += timeUntilNextQuery;
yield sleep(timeUntilNextQuery);
}
MagicQuerier.requestCount++;
if (axios)
return this.queryAxios(apiPath, query, post);
else
return this.queryFetch(apiPath, query, post);
});
}
queryFetch(apiPath, query, post) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const cleanParams = {};
for (const [key, value] of Object.entries(query !== null && query !== void 0 ? query : {}))
if (value !== undefined)
cleanParams[key] = value;
const searchParams = query ? `?${new URLSearchParams(cleanParams).toString()}` : '';
const url = `${IScry_1.ENDPOINT_API}/${apiPath}` + searchParams;
let result = yield fetch(url, {
body: JSON.stringify(post),
headers: Object.assign(Object.assign({ 'Content-Type': 'application/json' }, !MagicQuerier.agent ? undefined : {
'User-Agent': MagicQuerier.agent,
}), { Accept: "*/*" }),
method: post ? "POST" : "GET",
});
let lastError;
if (result !== undefined && !result.ok) {
const error = yield result.json();
lastError = new Error((_a = error.details) !== null && _a !== void 0 ? _a : error.code);
Object.assign(lastError, error);
result = undefined;
}
return { result: yield (result === null || result === void 0 ? void 0 : result.json()), lastError };
});
}
queryAxios(apiPath, query, post) {
return __awaiter(this, void 0, void 0, function* () {
let lastError;
const result = (yield axios.request({
data: post,
method: post ? "POST" : "GET",
params: query,
url: `${IScry_1.ENDPOINT_API}/${apiPath}`,
}).catch(({ response }) => {
var _a;
const error = response.data;
lastError = new Error((_a = error.details) !== null && _a !== void 0 ? _a : error.code);
Object.assign(lastError, response.data);
})) || undefined;
return { result: result === null || result === void 0 ? void 0 : result.data, lastError };
});
}
canRetry(error) {
if (error.code === "not_found" || error.code === "bad_request")
return false;
return !MagicQuerier.retry.canRetry || MagicQuerier.retry.canRetry(error);
}
}
exports.default = MagicQuerier;
MagicQuerier.lastQuery = 0;
MagicQuerier.retry = { attempts: 1 };
MagicQuerier.timeout = exports.defaultRequestTimeout;
MagicQuerier.requestCount = 0;