balena-sdk
Version:
The Balena JavaScript SDK
202 lines (201 loc) • 6.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.limitedMap = exports.groupByMap = exports.isNotFoundResponse = exports.isUnauthorizedResponse = exports.withSupervisorLockedError = exports.isFullUuid = exports.isId = exports.onlyIf = exports.notImplemented = void 0;
exports.mergePineOptions = mergePineOptions;
exports.delay = delay;
const tslib_1 = require("tslib");
const errors = tslib_1.__importStar(require("balena-errors"));
const notImplemented = () => {
throw new Error('The method is not implemented.');
};
exports.notImplemented = notImplemented;
const onlyIf = (condition) => (fn) => {
if (condition) {
return fn;
}
else {
return exports.notImplemented;
}
};
exports.onlyIf = onlyIf;
const isId = (v) => typeof v === 'number';
exports.isId = isId;
const isFullUuid = (v) => typeof v === 'string' && (v.length === 32 || v.length === 62);
exports.isFullUuid = isFullUuid;
const SUPERVISOR_LOCKED_STATUS_CODE = 423;
const withSupervisorLockedError = async (fn) => {
try {
return await fn();
}
catch (err) {
if (err.statusCode === SUPERVISOR_LOCKED_STATUS_CODE) {
throw new errors.BalenaSupervisorLockedError();
}
throw err;
}
};
exports.withSupervisorLockedError = withSupervisorLockedError;
const isBalenaRequestErrorResponseWithCode = (error, statusCode) => error.code === 'BalenaRequestError' && error.statusCode === statusCode;
const isUnauthorizedResponse = (err) => isBalenaRequestErrorResponseWithCode(err, 401);
exports.isUnauthorizedResponse = isUnauthorizedResponse;
const isNotFoundResponse = (err) => isBalenaRequestErrorResponseWithCode(err, 404);
exports.isNotFoundResponse = isNotFoundResponse;
const passthroughPineOptionKeys = ['$top', '$skip', '$orderby'];
function mergePineOptions(defaults, extras) {
if (extras == null || Object.keys(extras).length === 0) {
// @ts-expect-error - we own the merged response and know what it is gonna be
return defaults;
}
const result = { ...defaults };
if (extras.$select != null) {
const extraSelect = extras.$select == null ||
Array.isArray(extras.$select) ||
extras.$select === '*'
? // TS should be able to infer this
extras.$select
: [extras.$select];
if (extraSelect === '*') {
result.$select = '*';
}
else {
result.$select = [
...(typeof result.$select === 'string'
? [result.$select]
: Array.isArray(result.$select)
? result.$select
: []),
...(extraSelect !== null && extraSelect !== void 0 ? extraSelect : []),
];
}
}
for (const key of passthroughPineOptionKeys) {
if (key in extras) {
// @ts-expect-error TS doesn't realize that for the same key the values are compatible
result[key] = extras[key];
}
}
if (extras.$filter != null) {
result.$filter =
defaults.$filter != null
? {
$and: [defaults.$filter, extras.$filter],
}
: extras.$filter;
}
if (extras.$expand != null) {
result.$expand = mergeExpandOptions(defaults.$expand, extras.$expand);
}
return result;
}
const mergeExpandOptions = (defaultExpand, extraExpand) => {
var _a;
if (defaultExpand == null) {
return extraExpand;
}
// We only need to clone the defaultExpand as it's the only one we mutate
const $defaultExpand = convertExpandToObject(defaultExpand, true);
const $extraExpand = convertExpandToObject(extraExpand);
if ($extraExpand != null) {
for (const expandKey of Object.keys($extraExpand)) {
$defaultExpand[expandKey] = mergePineOptions((_a = $defaultExpand[expandKey]) !== null && _a !== void 0 ? _a : {}, $extraExpand[expandKey]);
}
}
return $defaultExpand;
};
function isArray(value) {
// See: https://github.com/microsoft/TypeScript/issues/17002
return Array.isArray(value);
}
// Converts a valid expand object in any format into a new object
// containing (at most) $expand, $filter and $select keys
const convertExpandToObject = (expandOption, cloneIfNeeded = false) => {
if (expandOption == null) {
return {};
}
if (typeof expandOption === 'string') {
return {
[expandOption]: {},
};
}
if (isArray(expandOption)) {
// Reduce the array into a single object
return expandOption.reduce((result, option) => Object.assign(result, typeof option === 'string' ? { [option]: {} } : option), {});
}
if (cloneIfNeeded) {
return { ...expandOption };
}
return expandOption;
};
/**
* Useful when you want to avoid having to manually parse the key
* or when need order guarantees while iterating the keys.
* @private
*/
const groupByMap = (entries, iteratee) => {
const result = new Map();
for (const entry of entries) {
const key = iteratee(entry);
let keyGroup = result.get(key);
if (keyGroup == null) {
keyGroup = [];
result.set(key, keyGroup);
}
keyGroup.push(entry);
}
return result;
};
exports.groupByMap = groupByMap;
async function delay(ms) {
let timerId;
try {
await new Promise((resolve) => {
timerId = setTimeout(resolve, ms);
});
}
finally {
if (timerId != null) {
clearTimeout(timerId);
}
}
}
const DEFAULT_CONCURRENCY_LIMIT = 50;
const limitedMap = (arr, fn, { concurrency = DEFAULT_CONCURRENCY_LIMIT, } = {}) => {
if (concurrency >= arr.length) {
return Promise.all(arr.map(fn));
}
return new Promise((resolve, reject) => {
const result = new Array(arr.length);
let inFlight = 0;
let idx = 0;
const runNext = async () => {
// Store the idx to use for this call before incrementing the main counter
const i = idx;
idx++;
if (i >= arr.length) {
return;
}
try {
inFlight++;
result[i] = await fn(arr[i], i, arr);
void runNext();
}
catch (err) {
// Stop any further iterations
idx = arr.length;
// Clear the results so far for gc
result.length = 0;
reject(err);
}
finally {
inFlight--;
if (inFlight === 0) {
resolve(result);
}
}
};
while (inFlight < concurrency) {
void runNext();
}
});
};
exports.limitedMap = limitedMap;