@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
212 lines • 8.96 kB
JavaScript
import { RestError } from '@azure/core-rest-pipeline';
import { createSerializer as createSerializerOrig } from '@azure/core-client';
import { pause } from './other.js';
import semverSatisfies from './semver-satisfies.js';
import { InternalError, UnexpectedTsError, UnsupportedVersionError } from './errors.js';
const bigIntPrefix = '_sdk-big-int-';
export const createSerializer = (...args) => {
const serializer = createSerializerOrig(...args);
const {
serialize,
deserialize
} = serializer;
return Object.assign(serializer, {
serialize(...[mapper, object, objectName, options]) {
// @ts-expect-error we are extending autorest with BigInt support
if (mapper.type.name !== 'BigInt' || object == null) {
return serialize.call(this, mapper, object, objectName, options);
}
if (typeof object !== 'bigint') {
objectName !== null && objectName !== void 0 ? objectName : objectName = mapper.serializedName;
throw new Error(`${objectName} with value ${object} must be of type bigint.`);
}
return object.toString();
},
deserialize(...[mapper, responseBody, objectName, options]) {
// @ts-expect-error we are extending autorest with BigInt support
if (mapper.type.name !== 'BigInt' || responseBody == null) {
if (typeof responseBody === 'string' && responseBody.startsWith(bigIntPrefix)) {
console.warn(`AeSdk internal error: BigInt value ${responseBody} handled incorrectly`);
responseBody = +responseBody.replace(bigIntPrefix, '');
}
const result = deserialize.call(this, mapper, responseBody, objectName, options);
// TODO: remove after fixing https://github.com/aeternity/ae_mdw/issues/1891
// and https://github.com/aeternity/aeternity/issues/4386
if (result instanceof Date) return new Date(+result / 1000);
return result;
}
if (typeof responseBody === 'number' && responseBody > Number.MAX_SAFE_INTEGER) {
throw new InternalError(`Number ${responseBody} is not accurate to be converted to BigInt`);
}
return BigInt(responseBody.toString().replace(bigIntPrefix, ''));
}
});
};
const safeLength = Number.MAX_SAFE_INTEGER.toString().length;
const bigIntPropertyRe = new RegExp(String.raw`("\w+":\s*)(\d{${safeLength},})(\s*[,}])`, 'm');
const bigIntArrayItemRe = new RegExp(String.raw`([[,]\s*)(\d{${safeLength},})\b`, 'm');
export const parseBigIntPolicy = {
name: 'parse-big-int',
async sendRequest(request, next) {
const response = await next(request);
if (response.bodyAsText == null) return response;
// TODO: replace with https://caniuse.com/mdn-javascript_builtins_json_parse_reviver_parameter_context_argument when it gets support in FF and Safari
response.bodyAsText = response.bodyAsText.replaceAll(new RegExp(bigIntPropertyRe, 'g'), matched => {
const match = matched.match(bigIntPropertyRe);
if (match == null) throw new UnexpectedTsError();
const [, name, value, end] = match;
return [name, +value > Number.MAX_SAFE_INTEGER ? `"${bigIntPrefix}${value}"` : value, end].join('');
});
// FIXME: may break strings inside json
response.bodyAsText = response.bodyAsText.replaceAll(new RegExp(bigIntArrayItemRe, 'g'), matched => {
const match = matched.match(bigIntArrayItemRe);
if (match == null) throw new UnexpectedTsError();
const [, prefix, value] = match;
return `${prefix}"${bigIntPrefix}${value}"`;
});
return response;
}
};
export const genRequestQueuesPolicy = () => {
const requestQueues = new Map();
return {
policy: {
name: 'request-queues',
async sendRequest(request, next) {
var _requestQueues$get;
const key = request.headers.get('__queue');
request.headers.delete('__queue');
const getResponse = async () => next(request);
if (key == null) return getResponse();
const req = ((_requestQueues$get = requestQueues.get(key)) !== null && _requestQueues$get !== void 0 ? _requestQueues$get : Promise.resolve()).then(getResponse);
requestQueues.set(key, req.catch(() => {}));
return req;
}
},
position: 'perCall'
};
};
export const genCombineGetRequestsPolicy = () => {
const pendingGetRequests = new Map();
return {
policy: {
name: 'combine-get-requests',
async sendRequest(request, next) {
var _pendingGetRequests$g;
if (request.method !== 'GET') return next(request);
const key = JSON.stringify([request.url, request.body]);
const response = (_pendingGetRequests$g = pendingGetRequests.get(key)) !== null && _pendingGetRequests$g !== void 0 ? _pendingGetRequests$g : next(request);
pendingGetRequests.set(key, response);
try {
return await response;
} finally {
pendingGetRequests.delete(key);
}
}
},
position: 'perCall'
};
};
export const genAggressiveCacheGetResponsesPolicy = () => {
const getRequests = new Map();
return {
policy: {
name: 'aggressive-cache-get-responses',
async sendRequest(request, next) {
var _getRequests$get;
if (request.method !== 'GET') return next(request);
const key = JSON.stringify([request.url, request.body]);
const response = (_getRequests$get = getRequests.get(key)) !== null && _getRequests$get !== void 0 ? _getRequests$get : next(request);
getRequests.set(key, response);
return response;
}
},
position: 'perCall'
};
};
export const genErrorFormatterPolicy = getMessage => ({
policy: {
name: 'error-formatter',
async sendRequest(request, next) {
try {
return await next(request);
} catch (error) {
if (!(error instanceof RestError) || error.request == null || error.message.startsWith('Error ')) throw error;
const prefix = `${new URL(error.request.url).pathname.slice(1)} error`;
if (error.response?.bodyAsText == null) {
if (error.message === '') error.message = `${prefix}: ${error.code}`;
throw error;
}
const body = error.response.parsedBody;
error.message = prefix;
const message = body == null ? ` ${error.response.status} status code` : getMessage(body);
if (message !== '') error.message += `:${message}`;
throw error;
}
}
},
position: 'perCall'
});
export const genVersionCheckPolicy = (name, versionCb, geVersion, ltVersion, ignoreVersion) => ({
policy: {
name: 'version-check',
async sendRequest(request, next) {
if (request.headers.has('__version-check')) {
request.headers.delete('__version-check');
return next(request);
}
const options = {
requestOptions: {
customHeaders: {
'__version-check': 'true'
}
}
};
const args = [await versionCb(options), geVersion, ltVersion];
if (!semverSatisfies(...args)) {
const error = new UnsupportedVersionError(name, ...args);
if (ignoreVersion) console.warn(error.message);else throw error;
}
return next(request);
}
},
position: 'perCall'
});
export const genRetryOnFailurePolicy = (retryCount, retryOverallDelay) => ({
policy: {
name: 'retry-on-failure',
async sendRequest(request, next) {
var _request$headers$get;
if (request.headers.get('__no-retry') != null) {
request.headers.delete('__no-retry');
return next(request);
}
const retryCode = (_request$headers$get = request.headers.get('__retry-code')) !== null && _request$headers$get !== void 0 ? _request$headers$get : NaN;
request.headers.delete('__retry-code');
const statusesToNotRetry = [200, 400, 403, 410, 500].filter(c => c !== +retryCode);
const intervals = new Array(retryCount).fill(0).map((_, idx) => ((idx + 1) / retryCount) ** 2);
const intervalSum = intervals.reduce((a, b) => a + b, 0);
const intervalsInMs = intervals.map(e => Math.floor(e / intervalSum * retryOverallDelay));
let error = new RestError('Not expected to be thrown');
for (let attempt = 0; attempt <= retryCount; attempt += 1) {
if (attempt !== 0) {
await pause(intervalsInMs[attempt - 1]);
const urlParsed = new URL(request.url);
urlParsed.searchParams.set('__sdk-retry', attempt.toString());
request.url = urlParsed.toString();
}
try {
return await next(request);
} catch (e) {
var _e$response$status;
if (!(e instanceof RestError)) throw e;
if (statusesToNotRetry.includes((_e$response$status = e.response?.status) !== null && _e$response$status !== void 0 ? _e$response$status : 0)) throw e;
error = e;
}
}
throw error;
}
},
position: 'perCall'
});
//# sourceMappingURL=autorest.js.map