UNPKG

balena-sdk

Version:
202 lines (201 loc) • 6.96 kB
"use strict"; 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 = Object.assign({}, 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 Object.assign({}, 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;