@chainlink/external-adapter-framework 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/background-executor.d.ts +1 -3
- package/background-executor.js +9 -4
- package/config/index.d.ts +1 -1
- package/examples/coingecko/src/config/index.d.ts +2 -0
- package/examples/coingecko/src/config/index.js +1 -9
- package/examples/coingecko/src/config/overrides.json +0 -1
- package/examples/coingecko/src/cryptoUtils.d.ts +31 -0
- package/examples/coingecko/src/cryptoUtils.js +20 -1
- package/examples/coingecko/src/endpoint/coins.d.ts +9 -0
- package/examples/coingecko/src/endpoint/coins.js +3 -2
- package/examples/coingecko/src/endpoint/crypto-marketcap.d.ts +3 -0
- package/examples/coingecko/src/endpoint/crypto-marketcap.js +5 -22
- package/examples/coingecko/src/endpoint/crypto-volume.d.ts +3 -0
- package/examples/coingecko/src/endpoint/crypto-volume.js +5 -22
- package/examples/coingecko/src/endpoint/crypto.d.ts +3 -0
- package/examples/coingecko/src/endpoint/crypto.js +5 -25
- package/examples/coingecko/src/endpoint/dominance.d.ts +3 -0
- package/examples/coingecko/src/endpoint/dominance.js +4 -3
- package/examples/coingecko/src/endpoint/global-marketcap.d.ts +3 -0
- package/examples/coingecko/src/endpoint/global-marketcap.js +4 -3
- package/examples/coingecko/src/endpoint/index.d.ts +6 -0
- package/examples/coingecko/src/globalUtils.d.ts +27 -0
- package/examples/coingecko/src/globalUtils.js +6 -8
- package/examples/coingecko/src/index.d.ts +4 -0
- package/examples/coingecko/src/index.js +7 -3
- package/examples/coingecko-old/batch-warming.d.ts +7 -0
- package/examples/coingecko-old/index.d.ts +2 -0
- package/examples/coingecko-old/rest.d.ts +12 -0
- package/index.d.ts +2 -2
- package/index.js +24 -7
- package/metrics/index.js +7 -3
- package/package.json +10 -4
- package/rate-limiting/index.d.ts +2 -2
- package/transports/batch-warming.d.ts +1 -1
- package/transports/batch-warming.js +1 -1
- package/transports/index.d.ts +3 -1
- package/transports/index.js +3 -1
- package/transports/metrics.d.ts +1 -1
- package/util/request.d.ts +0 -1
- package/validation/index.js +4 -2
package/background-executor.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { Server } from 'http';
|
|
3
1
|
import { Adapter } from './adapter';
|
|
4
2
|
/**
|
|
5
3
|
* Very simple background loop that will call the [[Transport.backgroundExecute]] functions in all Transports.
|
|
@@ -8,4 +6,4 @@ import { Adapter } from './adapter';
|
|
|
8
6
|
* @param adapter - an initialized External Adapter
|
|
9
7
|
* @param server - the http server to attach an on close listener to
|
|
10
8
|
*/
|
|
11
|
-
export declare function callBackgroundExecutes(adapter: Adapter,
|
|
9
|
+
export declare function callBackgroundExecutes(adapter: Adapter, apiShutdownPromise?: Promise<void>): Promise<void>;
|
package/background-executor.js
CHANGED
|
@@ -10,12 +10,18 @@ const logger = (0, util_1.makeLogger)('BackgroundExecutor');
|
|
|
10
10
|
* @param adapter - an initialized External Adapter
|
|
11
11
|
* @param server - the http server to attach an on close listener to
|
|
12
12
|
*/
|
|
13
|
-
async function callBackgroundExecutes(adapter,
|
|
13
|
+
async function callBackgroundExecutes(adapter, apiShutdownPromise) {
|
|
14
14
|
// Set up variable to check later on to see if we need to stop this background "thread"
|
|
15
15
|
// If no server is provided, the listener won't be set and serverClosed will always be false
|
|
16
16
|
let serverClosed = false;
|
|
17
|
-
|
|
17
|
+
const timeoutsMap = {};
|
|
18
|
+
apiShutdownPromise?.then(() => {
|
|
18
19
|
serverClosed = true;
|
|
20
|
+
for (const endpointName in timeoutsMap) {
|
|
21
|
+
logger.debug(`Clearing timeout for endpoint "${endpointName}"`);
|
|
22
|
+
timeoutsMap[endpointName].unref();
|
|
23
|
+
clearTimeout(timeoutsMap[endpointName]);
|
|
24
|
+
}
|
|
19
25
|
});
|
|
20
26
|
for (const endpoint of adapter.endpoints) {
|
|
21
27
|
const backgroundExecute = endpoint.transport.backgroundExecute?.bind(endpoint.transport);
|
|
@@ -35,8 +41,7 @@ async function callBackgroundExecutes(adapter, server) {
|
|
|
35
41
|
logger.debug(`Calling background execute for endpoint "${endpoint.name}"`);
|
|
36
42
|
const timeToWait = await backgroundExecute(context);
|
|
37
43
|
logger.debug(`Finished background execute for endpoint "${endpoint.name}", sleeping for ${timeToWait}ms`);
|
|
38
|
-
|
|
39
|
-
handler();
|
|
44
|
+
timeoutsMap[endpoint.name] = setTimeout(handler, timeToWait);
|
|
40
45
|
};
|
|
41
46
|
// Start recursive async calls
|
|
42
47
|
handler();
|
package/config/index.d.ts
CHANGED
|
@@ -159,7 +159,7 @@ export declare const BaseSettings: {
|
|
|
159
159
|
readonly description: "Maximum amount of characters that the common part of the cache key or feed ID can have";
|
|
160
160
|
readonly type: "number";
|
|
161
161
|
readonly default: 300;
|
|
162
|
-
readonly validate: (value?: number
|
|
162
|
+
readonly validate: (value?: number) => "MAX_COMMON_KEY_SIZE must be a number between 150 and 500" | undefined;
|
|
163
163
|
};
|
|
164
164
|
readonly REST_TRANSPORT_MAX_RATE_LIMIT_RETRIES: {
|
|
165
165
|
readonly description: "Maximum amount of times the Rest Transport will attempt to set up a request when blocked by the rate limiter";
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.NAME = 'COINGECKO';
|
|
5
|
-
exports.DEFAULT_ENDPOINT = 'crypto';
|
|
3
|
+
exports.PRO_API_ENDPOINT = exports.DEFAULT_API_ENDPOINT = void 0;
|
|
6
4
|
exports.DEFAULT_API_ENDPOINT = 'https://api.coingecko.com/api/v3';
|
|
7
5
|
exports.PRO_API_ENDPOINT = 'https://pro-api.coingecko.com/api/v3';
|
|
8
|
-
exports.customSettings = {
|
|
9
|
-
API_KEY: {
|
|
10
|
-
description: 'API key for the CoinGecko API',
|
|
11
|
-
type: 'string',
|
|
12
|
-
},
|
|
13
|
-
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
import { AdapterConfig } from '../../../config';
|
|
3
|
+
import { InputParameters } from '../../../validation';
|
|
4
|
+
export interface CryptoRequestParams {
|
|
5
|
+
coinid?: string;
|
|
6
|
+
base?: string;
|
|
7
|
+
quote: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const cryptoInputParams: InputParameters;
|
|
10
|
+
export interface ProviderRequestBody {
|
|
11
|
+
ids: string;
|
|
12
|
+
vs_currencies: string;
|
|
13
|
+
include_market_cap?: boolean;
|
|
14
|
+
include_24hr_vol?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ProviderResponseBody {
|
|
17
|
+
[base: string]: {
|
|
18
|
+
[quote: string]: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface ResultEntryWithoutOverrides {
|
|
22
|
+
value: number;
|
|
23
|
+
params: {
|
|
24
|
+
quote: string;
|
|
25
|
+
base?: string;
|
|
26
|
+
coinid?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare const buildBatchedRequestBody: (params: CryptoRequestParams[], config: AdapterConfig) => AxiosRequestConfig<ProviderRequestBody>;
|
|
30
|
+
export declare const constructEntry: (res: AxiosResponse<ProviderResponseBody>, requestPayload: CryptoRequestParams, resultPath: string) => ResultEntryWithoutOverrides | undefined;
|
|
31
|
+
export {};
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.constructEntry = exports.buildBatchedRequestBody = void 0;
|
|
3
|
+
exports.constructEntry = exports.buildBatchedRequestBody = exports.cryptoInputParams = void 0;
|
|
4
4
|
const config_1 = require("./config");
|
|
5
5
|
const logger_1 = require("../../../util/logger");
|
|
6
|
+
exports.cryptoInputParams = {
|
|
7
|
+
coinid: {
|
|
8
|
+
description: 'The CoinGecko id or to query',
|
|
9
|
+
type: 'string',
|
|
10
|
+
required: false,
|
|
11
|
+
},
|
|
12
|
+
base: {
|
|
13
|
+
aliases: ['from', 'coin'],
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'The symbol of symbols of the currency to query',
|
|
16
|
+
required: false,
|
|
17
|
+
},
|
|
18
|
+
quote: {
|
|
19
|
+
aliases: ['to', 'market'],
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'The symbol of the currency to convert to',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
6
25
|
const buildBatchedRequestBody = (params, config) => {
|
|
7
26
|
return {
|
|
8
27
|
baseURL: config.API_KEY ? config_1.PRO_API_ENDPOINT : config_1.DEFAULT_API_ENDPOINT,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AdapterEndpoint } from '../../../../../src/adapter';
|
|
2
|
+
import { InputParameters } from '../../../../../src/validation';
|
|
3
|
+
export declare const inputParameters: InputParameters;
|
|
4
|
+
export interface CoinsResponse {
|
|
5
|
+
id: string;
|
|
6
|
+
symbol: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const endpoint: AdapterEndpoint<import("../../../../util/request").AdapterRequestData, CoinsResponse[], import("../../../../config").SettingsMap>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.endpoint = exports.inputParameters = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../../src/adapter");
|
|
4
5
|
const transports_1 = require("../../../../../src/transports");
|
|
5
6
|
const config_1 = require("../config");
|
|
6
7
|
exports.inputParameters = {};
|
|
@@ -26,8 +27,8 @@ const restEndpointTransport = new transports_1.RestTransport({
|
|
|
26
27
|
coalescing: true,
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
|
-
exports.endpoint = {
|
|
30
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
30
31
|
name: 'coins',
|
|
31
32
|
transport: restEndpointTransport,
|
|
32
33
|
inputParameters: exports.inputParameters,
|
|
33
|
-
};
|
|
34
|
+
});
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.endpoint =
|
|
3
|
+
exports.endpoint = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../adapter");
|
|
4
5
|
const batch_warming_1 = require("../../../../transports/batch-warming");
|
|
5
|
-
const input_params_1 = require("../../../../validation/input-params");
|
|
6
6
|
const cryptoUtils_1 = require("../cryptoUtils");
|
|
7
|
-
exports.inputParameters = {
|
|
8
|
-
overrides: input_params_1.baseInputParameters['overrides'],
|
|
9
|
-
coinid: {
|
|
10
|
-
description: 'The CoinGecko id or to query',
|
|
11
|
-
required: false,
|
|
12
|
-
},
|
|
13
|
-
base: {
|
|
14
|
-
aliases: ['from', 'coin'],
|
|
15
|
-
description: 'The symbol of symbols of the currency to query',
|
|
16
|
-
required: false,
|
|
17
|
-
},
|
|
18
|
-
quote: {
|
|
19
|
-
aliases: ['to', 'market'],
|
|
20
|
-
description: 'The symbol of the currency to convert to',
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
7
|
const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
25
8
|
prepareRequest: (params, context) => {
|
|
26
9
|
const requestBody = (0, cryptoUtils_1.buildBatchedRequestBody)(params, context.adapterConfig);
|
|
@@ -38,9 +21,9 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
38
21
|
return entries;
|
|
39
22
|
},
|
|
40
23
|
});
|
|
41
|
-
exports.endpoint = {
|
|
24
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
42
25
|
name: 'marketcap',
|
|
43
26
|
aliases: ['crypto-marketcap'],
|
|
44
27
|
transport: batchEndpointTransport,
|
|
45
|
-
inputParameters:
|
|
46
|
-
};
|
|
28
|
+
inputParameters: cryptoUtils_1.cryptoInputParams,
|
|
29
|
+
});
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.endpoint =
|
|
3
|
+
exports.endpoint = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../adapter");
|
|
4
5
|
const batch_warming_1 = require("../../../../transports/batch-warming");
|
|
5
|
-
const input_params_1 = require("../../../../validation/input-params");
|
|
6
6
|
const cryptoUtils_1 = require("../cryptoUtils");
|
|
7
|
-
exports.inputParameters = {
|
|
8
|
-
overrides: input_params_1.baseInputParameters['overrides'],
|
|
9
|
-
coinid: {
|
|
10
|
-
description: 'The CoinGecko id or to query',
|
|
11
|
-
required: false,
|
|
12
|
-
},
|
|
13
|
-
base: {
|
|
14
|
-
aliases: ['from', 'coin'],
|
|
15
|
-
description: 'The symbol of symbols of the currency to query',
|
|
16
|
-
required: false,
|
|
17
|
-
},
|
|
18
|
-
quote: {
|
|
19
|
-
aliases: ['to', 'market'],
|
|
20
|
-
description: 'The symbol of the currency to convert to',
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
7
|
const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
25
8
|
prepareRequest: (params, context) => {
|
|
26
9
|
const requestBody = (0, cryptoUtils_1.buildBatchedRequestBody)(params, context.adapterConfig);
|
|
@@ -38,9 +21,9 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
38
21
|
return entries;
|
|
39
22
|
},
|
|
40
23
|
});
|
|
41
|
-
exports.endpoint = {
|
|
24
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
42
25
|
name: 'volume',
|
|
43
26
|
aliases: ['crypto-volume'],
|
|
44
27
|
transport: batchEndpointTransport,
|
|
45
|
-
inputParameters:
|
|
46
|
-
};
|
|
28
|
+
inputParameters: cryptoUtils_1.cryptoInputParams,
|
|
29
|
+
});
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.endpoint =
|
|
3
|
+
exports.endpoint = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../adapter");
|
|
4
5
|
const batch_warming_1 = require("../../../../transports/batch-warming");
|
|
5
|
-
const input_params_1 = require("../../../../validation/input-params");
|
|
6
6
|
const cryptoUtils_1 = require("../cryptoUtils");
|
|
7
|
-
exports.inputParameters = {
|
|
8
|
-
overrides: input_params_1.baseInputParameters['overrides'],
|
|
9
|
-
coinid: {
|
|
10
|
-
description: 'The CoinGecko id or to query',
|
|
11
|
-
required: false,
|
|
12
|
-
},
|
|
13
|
-
base: {
|
|
14
|
-
aliases: ['from', 'coin'],
|
|
15
|
-
description: 'The symbol of symbols of the currency to query',
|
|
16
|
-
required: false,
|
|
17
|
-
},
|
|
18
|
-
quote: {
|
|
19
|
-
aliases: ['to', 'market'],
|
|
20
|
-
description: 'The symbol of the currency to convert to',
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
7
|
const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
25
8
|
prepareRequest: (params, context) => {
|
|
26
9
|
return (0, cryptoUtils_1.buildBatchedRequestBody)(params, context.adapterConfig);
|
|
@@ -29,9 +12,6 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
29
12
|
const entries = [];
|
|
30
13
|
for (const requestPayload of params) {
|
|
31
14
|
const entry = (0, cryptoUtils_1.constructEntry)(res, requestPayload, requestPayload.quote.toLowerCase());
|
|
32
|
-
// NOTE: the `entries` array is going to be shorter than the `params` array if one of the params has a bad token.
|
|
33
|
-
// Is that okay? I did this to avoid caching an invalid value for transient DP issues. Just want to make
|
|
34
|
-
// sure this works within the core framework
|
|
35
15
|
if (entry) {
|
|
36
16
|
entries.push(entry);
|
|
37
17
|
}
|
|
@@ -39,9 +19,9 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
39
19
|
return entries;
|
|
40
20
|
},
|
|
41
21
|
});
|
|
42
|
-
exports.endpoint = {
|
|
22
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
43
23
|
name: 'crypto',
|
|
44
24
|
aliases: ['crypto-batched', 'batched', 'batch'],
|
|
45
25
|
transport: batchEndpointTransport,
|
|
46
|
-
inputParameters:
|
|
47
|
-
};
|
|
26
|
+
inputParameters: cryptoUtils_1.cryptoInputParams,
|
|
27
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.endpoint = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../adapter");
|
|
4
5
|
const batch_warming_1 = require("../../../../transports/batch-warming");
|
|
5
6
|
const globalUtils_1 = require("../globalUtils");
|
|
6
7
|
const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
7
8
|
prepareRequest: (params, context) => {
|
|
8
|
-
return (0, globalUtils_1.buildGlobalRequestBody)(context.adapterConfig);
|
|
9
|
+
return (0, globalUtils_1.buildGlobalRequestBody)(context.adapterConfig.API_KEY);
|
|
9
10
|
},
|
|
10
11
|
parseResponse: (params, res) => {
|
|
11
12
|
const entries = [];
|
|
@@ -18,9 +19,9 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
18
19
|
return entries;
|
|
19
20
|
},
|
|
20
21
|
});
|
|
21
|
-
exports.endpoint = {
|
|
22
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
22
23
|
name: 'dominance',
|
|
23
24
|
aliases: ['market_cap_percentage'],
|
|
24
25
|
transport: batchEndpointTransport,
|
|
25
26
|
inputParameters: globalUtils_1.inputParameters,
|
|
26
|
-
};
|
|
27
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.endpoint = void 0;
|
|
4
|
+
const adapter_1 = require("../../../../adapter");
|
|
4
5
|
const batch_warming_1 = require("../../../../transports/batch-warming");
|
|
5
6
|
const globalUtils_1 = require("../globalUtils");
|
|
6
7
|
const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
7
8
|
prepareRequest: (params, context) => {
|
|
8
|
-
return (0, globalUtils_1.buildGlobalRequestBody)(context.adapterConfig);
|
|
9
|
+
return (0, globalUtils_1.buildGlobalRequestBody)(context.adapterConfig.API_KEY);
|
|
9
10
|
},
|
|
10
11
|
parseResponse: (params, res) => {
|
|
11
12
|
const entries = [];
|
|
@@ -18,9 +19,9 @@ const batchEndpointTransport = new batch_warming_1.BatchWarmingTransport({
|
|
|
18
19
|
return entries;
|
|
19
20
|
},
|
|
20
21
|
});
|
|
21
|
-
exports.endpoint = {
|
|
22
|
+
exports.endpoint = new adapter_1.AdapterEndpoint({
|
|
22
23
|
name: 'globalmarketcap',
|
|
23
24
|
aliases: ['total_market_cap'],
|
|
24
25
|
transport: batchEndpointTransport,
|
|
25
26
|
inputParameters: globalUtils_1.inputParameters,
|
|
26
|
-
};
|
|
27
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { endpoint as coins } from './coins';
|
|
2
|
+
export { endpoint as crypto } from './crypto';
|
|
3
|
+
export { endpoint as cryptoMarketcap } from './crypto-marketcap';
|
|
4
|
+
export { endpoint as globalMarketcap } from './global-marketcap';
|
|
5
|
+
export { endpoint as dominance } from './dominance';
|
|
6
|
+
export { endpoint as cryptoVolume } from './crypto-volume';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
import { InputParameters } from '../../../validation';
|
|
3
|
+
export declare const inputParameters: InputParameters;
|
|
4
|
+
export interface AdapterRequestParams {
|
|
5
|
+
market: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderResponseBody {
|
|
8
|
+
data: {
|
|
9
|
+
active_cryptocurrencies: number;
|
|
10
|
+
upcoming_icos: number;
|
|
11
|
+
ongoing_icos: number;
|
|
12
|
+
ended_icos: number;
|
|
13
|
+
markets: number;
|
|
14
|
+
total_market_cap: Record<string, number>;
|
|
15
|
+
total_volume: Record<string, number>;
|
|
16
|
+
market_cap_percentage: Record<string, number>;
|
|
17
|
+
market_cap_change_percentage_24h_usd: number;
|
|
18
|
+
updated_at: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface ResultEntry {
|
|
22
|
+
value: number;
|
|
23
|
+
params: AdapterRequestParams;
|
|
24
|
+
}
|
|
25
|
+
export declare const buildGlobalRequestBody: (apiKey?: string) => AxiosRequestConfig<never>;
|
|
26
|
+
export declare const constructEntry: (res: AxiosResponse<ProviderResponseBody>, requestPayload: AdapterRequestParams, resultPath: 'total_market_cap' | 'market_cap_percentage') => ResultEntry | undefined;
|
|
27
|
+
export {};
|
|
@@ -11,15 +11,14 @@ exports.inputParameters = {
|
|
|
11
11
|
required: true,
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
|
-
const buildGlobalRequestBody = (
|
|
15
|
-
const apiKey = {
|
|
16
|
-
x_cg_pro_api_key: config.API_KEY,
|
|
17
|
-
};
|
|
14
|
+
const buildGlobalRequestBody = (apiKey) => {
|
|
18
15
|
return {
|
|
19
|
-
baseURL:
|
|
16
|
+
baseURL: apiKey ? config_1.PRO_API_ENDPOINT : config_1.DEFAULT_API_ENDPOINT,
|
|
20
17
|
url: '/global',
|
|
21
18
|
method: 'GET',
|
|
22
|
-
params:
|
|
19
|
+
params: {
|
|
20
|
+
x_cg_pro_api_key: apiKey,
|
|
21
|
+
},
|
|
23
22
|
};
|
|
24
23
|
};
|
|
25
24
|
exports.buildGlobalRequestBody = buildGlobalRequestBody;
|
|
@@ -39,10 +38,9 @@ const constructEntry = (res, requestPayload, resultPath) => {
|
|
|
39
38
|
logger.warn(`Data for "${requestPayload.market}" not found".`);
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
42
|
-
|
|
41
|
+
return {
|
|
43
42
|
params: requestPayload,
|
|
44
43
|
value: result,
|
|
45
44
|
};
|
|
46
|
-
return entry;
|
|
47
45
|
};
|
|
48
46
|
exports.constructEntry = constructEntry;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Adapter } from '../../../../src/adapter';
|
|
3
|
+
export declare const adapter: Adapter<import("../../../config").SettingsMap>;
|
|
4
|
+
export declare const server: () => Promise<import("fastify").FastifyInstance<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault> | undefined>;
|
|
@@ -3,12 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.adapter = void 0;
|
|
6
|
+
exports.server = exports.adapter = void 0;
|
|
7
|
+
const __1 = require("../../..");
|
|
8
|
+
const adapter_1 = require("../../../../src/adapter");
|
|
7
9
|
const overrides_json_1 = __importDefault(require("./config/overrides.json"));
|
|
8
10
|
const endpoint_1 = require("./endpoint");
|
|
9
|
-
exports.adapter = {
|
|
11
|
+
exports.adapter = new adapter_1.Adapter({
|
|
10
12
|
defaultEndpoint: 'crypto',
|
|
11
13
|
name: 'coingecko',
|
|
12
14
|
endpoints: [endpoint_1.crypto, endpoint_1.coins, endpoint_1.cryptoMarketcap, endpoint_1.cryptoVolume, endpoint_1.dominance, endpoint_1.globalMarketcap],
|
|
13
15
|
overrides: overrides_json_1.default['coingecko'],
|
|
14
|
-
};
|
|
16
|
+
});
|
|
17
|
+
const server = () => (0, __1.expose)(exports.adapter);
|
|
18
|
+
exports.server = server;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AdapterEndpoint } from '../../adapter';
|
|
2
|
+
interface AdapterRequestParams {
|
|
3
|
+
base: string;
|
|
4
|
+
quote: string;
|
|
5
|
+
}
|
|
6
|
+
interface ProviderResponseBody {
|
|
7
|
+
[base: string]: {
|
|
8
|
+
[quote: string]: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare const restEndpoint: AdapterEndpoint<AdapterRequestParams, ProviderResponseBody, import("../../config").SettingsMap>;
|
|
12
|
+
export {};
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
2
|
import { Adapter, AdapterDependencies } from './adapter';
|
|
3
3
|
/**
|
|
4
4
|
* Main function for the framework.
|
|
@@ -8,4 +8,4 @@ import { Adapter, AdapterDependencies } from './adapter';
|
|
|
8
8
|
* @param dependencies - an optional object with adapter dependencies to inject
|
|
9
9
|
* @returns a Promise that resolves to the http.Server listening for connections
|
|
10
10
|
*/
|
|
11
|
-
export declare const expose: (adapter: Adapter, dependencies?: Partial<AdapterDependencies>
|
|
11
|
+
export declare const expose: (adapter: Adapter, dependencies?: Partial<AdapterDependencies>) => Promise<FastifyInstance | undefined>;
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.expose = void 0;
|
|
7
7
|
const fastify_1 = __importDefault(require("fastify"));
|
|
8
8
|
const path_1 = require("path");
|
|
9
|
+
const adapter_1 = require("./adapter");
|
|
9
10
|
const background_executor_1 = require("./background-executor");
|
|
10
11
|
const cache_1 = require("./cache");
|
|
11
12
|
const metrics_1 = require("./metrics");
|
|
@@ -24,25 +25,36 @@ const VERSION = process.env['npm_package_version'];
|
|
|
24
25
|
* @returns a Promise that resolves to the http.Server listening for connections
|
|
25
26
|
*/
|
|
26
27
|
const expose = async (adapter, dependencies) => {
|
|
28
|
+
if (!(adapter instanceof adapter_1.Adapter)) {
|
|
29
|
+
throw new Error('The adapter has not been initialized as an instance of the Adapter class, exiting.');
|
|
30
|
+
}
|
|
27
31
|
// Initialize adapter (create dependencies, inject them, build endpoint map, etc.)
|
|
28
32
|
await adapter.initialize(dependencies);
|
|
29
|
-
let
|
|
33
|
+
let api = undefined;
|
|
30
34
|
if (adapter.config.METRICS_ENABLED && adapter.config.EXPERIMENTAL_METRICS_ENABLED) {
|
|
31
35
|
(0, metrics_1.setupMetricsServer)(adapter.name, adapter.config);
|
|
32
36
|
}
|
|
37
|
+
// Optional Promise to indicate that the API is shutting down (for us to close background executors)
|
|
38
|
+
let apiShutdownPromise;
|
|
33
39
|
if (adapter.config.EA_MODE === 'reader' || adapter.config.EA_MODE === 'reader-writer') {
|
|
34
40
|
// Main REST API server to handle incoming requests
|
|
35
|
-
|
|
41
|
+
api = await buildRestApi(adapter);
|
|
42
|
+
// Add a hook on close to use on the background execution loop to stop it
|
|
43
|
+
apiShutdownPromise = new Promise((resolve) => {
|
|
44
|
+
api?.addHook('onClose', async () => resolve());
|
|
45
|
+
});
|
|
36
46
|
// Start listening for incoming requests
|
|
37
47
|
try {
|
|
38
|
-
await
|
|
48
|
+
await api.listen({
|
|
49
|
+
port: adapter.config.EA_PORT,
|
|
50
|
+
host: adapter.config.EA_HOST,
|
|
51
|
+
});
|
|
39
52
|
}
|
|
40
53
|
catch (err) {
|
|
41
54
|
logger.fatal(`There was an error when starting the EA server: ${err}`);
|
|
42
55
|
process.exit();
|
|
43
56
|
}
|
|
44
|
-
logger.info(`Listening on port ${
|
|
45
|
-
server = app.server;
|
|
57
|
+
logger.info(`Listening on port ${api.server.address().port}`);
|
|
46
58
|
}
|
|
47
59
|
else {
|
|
48
60
|
logger.info('REST API is disabled; this instance will not process incoming requests.');
|
|
@@ -50,12 +62,12 @@ const expose = async (adapter, dependencies) => {
|
|
|
50
62
|
if (adapter.config.EA_MODE === 'writer' || adapter.config.EA_MODE === 'reader-writer') {
|
|
51
63
|
// Start background loop that will take care of calling any async Transports
|
|
52
64
|
logger.info('Starting background execution loop');
|
|
53
|
-
(0, background_executor_1.callBackgroundExecutes)(adapter,
|
|
65
|
+
(0, background_executor_1.callBackgroundExecutes)(adapter, apiShutdownPromise);
|
|
54
66
|
}
|
|
55
67
|
else {
|
|
56
68
|
logger.info('Background executor is disabled; this instance will not perform async background executes.');
|
|
57
69
|
}
|
|
58
|
-
return
|
|
70
|
+
return api;
|
|
59
71
|
};
|
|
60
72
|
exports.expose = expose;
|
|
61
73
|
async function buildRestApi(adapter) {
|
|
@@ -66,6 +78,11 @@ async function buildRestApi(adapter) {
|
|
|
66
78
|
});
|
|
67
79
|
// Use global error handling
|
|
68
80
|
app.setErrorHandler(validation_1.errorCatchingMiddleware);
|
|
81
|
+
// Always reply with json content
|
|
82
|
+
app.addHook('preHandler', (_, reply, done) => {
|
|
83
|
+
reply.headers({ 'content-type': 'application/json; charset=utf-8' });
|
|
84
|
+
done();
|
|
85
|
+
});
|
|
69
86
|
const transportHandler = await (0, transports_1.buildTransportHandler)(adapter);
|
|
70
87
|
app.register(async (router) => {
|
|
71
88
|
// Set up "middlewares" (hooks in fastify)
|
package/metrics/index.js
CHANGED
|
@@ -46,7 +46,10 @@ function setupMetricsServer(name, config) {
|
|
|
46
46
|
res.type('txt');
|
|
47
47
|
res.send(await client.register.metrics());
|
|
48
48
|
});
|
|
49
|
-
metricsApp.listen(
|
|
49
|
+
metricsApp.listen({
|
|
50
|
+
port: metricsPort,
|
|
51
|
+
host: eaHost,
|
|
52
|
+
}, () => logger.info(`Monitoring listening on port ${metricsPort}!`));
|
|
50
53
|
}
|
|
51
54
|
exports.setupMetricsServer = setupMetricsServer;
|
|
52
55
|
const setupMetrics = (name, config) => {
|
|
@@ -64,8 +67,9 @@ exports.setupMetrics = setupMetrics;
|
|
|
64
67
|
* @returns the cache middleware function
|
|
65
68
|
*/
|
|
66
69
|
const buildMetricsMiddleware = (req, res, done) => {
|
|
67
|
-
|
|
68
|
-
const
|
|
70
|
+
// The request context can technically be empty if the input validation failed
|
|
71
|
+
const feedId = req.requestContext?.meta?.metrics?.feedId || 'N/A';
|
|
72
|
+
const labels = buildHttpRequestMetricsLabel(feedId, req.requestContext?.meta?.error, req.requestContext?.meta?.metrics?.cacheHit);
|
|
69
73
|
// Record number of requests sent to EA
|
|
70
74
|
exports.httpRequestsTotal.labels(labels).inc();
|
|
71
75
|
// Record response time of request through entire EA
|
package/package.json
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainlink/external-adapter-framework",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"axios": "^0.27.2",
|
|
8
|
-
"fastify": "^
|
|
8
|
+
"fastify": "^4.5.3",
|
|
9
9
|
"ioredis": "^5.0.4",
|
|
10
10
|
"pino": "^7.9.2",
|
|
11
11
|
"prom-client": "13.2.0",
|
|
12
|
+
"supertest": "^6.2.4",
|
|
13
|
+
"ts-jest": "^28.0.7",
|
|
12
14
|
"ts-node": "^10.9.1",
|
|
13
15
|
"typescript": "^4.6.3",
|
|
14
16
|
"ws": "^8.5.0"
|
|
15
17
|
},
|
|
16
18
|
"scripts": {
|
|
17
|
-
"start": "ts-node src/test.ts",
|
|
18
19
|
"generate-docs": "typedoc src/**/*.ts",
|
|
19
20
|
"test": "LOG_LEVEL=error EA_PORT=0 c8 ava",
|
|
20
21
|
"test-debug": "LOG_LEVEL=trace DEBUG=true EA_PORT=0 c8 ava --verbose",
|
|
21
22
|
"build": "tsc",
|
|
22
23
|
"lint": "eslint ./src && prettier --check ./src/**/*.ts",
|
|
23
|
-
"
|
|
24
|
+
"lint-fix": "eslint --fix ./src && prettier --write ./src/**/*.ts",
|
|
25
|
+
"dev": "NODE_ENV=develop tsnd --respawn --transpile-only --project tsconfig.json './src/test.ts'",
|
|
26
|
+
"verify": "yarn lint && yarn build && yarn build -p ./test/tsconfig.json && yarn test"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@sinonjs/fake-timers": "^9.1.2",
|
|
30
|
+
"@types/jest": "^28.1.7",
|
|
27
31
|
"@types/node": "^18.6.5",
|
|
28
32
|
"@types/sinonjs__fake-timers": "^8.1.2",
|
|
33
|
+
"@types/supertest": "^2.0.12",
|
|
29
34
|
"@types/ws": "^8.5.3",
|
|
30
35
|
"@typescript-eslint/eslint-plugin": "^5.17.0",
|
|
31
36
|
"@typescript-eslint/parser": "^5.17.0",
|
|
@@ -34,6 +39,7 @@
|
|
|
34
39
|
"eslint": "^8.14.0",
|
|
35
40
|
"eslint-config-prettier": "^8.5.0",
|
|
36
41
|
"eslint-plugin-tsdoc": "^0.2.16",
|
|
42
|
+
"jest": "^29.0.3",
|
|
37
43
|
"mock-socket": "^9.1.3",
|
|
38
44
|
"nock": "^13.2.4",
|
|
39
45
|
"pino-pretty": "^7.6.0",
|
package/rate-limiting/index.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface BackgroundExecuteRateLimiter extends RateLimiter {
|
|
|
44
44
|
* @param limits - the rate limit tier set for the adapter
|
|
45
45
|
* @returns the most restrictive of the set options, in requests per second
|
|
46
46
|
*/
|
|
47
|
-
export declare const consolidateTierLimits: (limits?: AdapterRateLimitTier
|
|
47
|
+
export declare const consolidateTierLimits: (limits?: AdapterRateLimitTier) => number;
|
|
48
48
|
/**
|
|
49
49
|
* Validates rate limiting tiers specified for the adapter, and returns the one to use.
|
|
50
50
|
*
|
|
@@ -52,4 +52,4 @@ export declare const consolidateTierLimits: (limits?: AdapterRateLimitTier | und
|
|
|
52
52
|
* @param tiers - the adapter config listing the different available API tiers
|
|
53
53
|
* @returns the specified API tier, or a default one if none are specified
|
|
54
54
|
*/
|
|
55
|
-
export declare const getRateLimitingTier: (config: AdapterConfig, tiers?: Record<string, AdapterRateLimitTier>
|
|
55
|
+
export declare const getRateLimitingTier: (config: AdapterConfig, tiers?: Record<string, AdapterRateLimitTier>) => AdapterRateLimitTier | undefined;
|
|
@@ -26,7 +26,7 @@ export declare class BatchWarmingTransport<AdapterParams, ProviderRequestBody, P
|
|
|
26
26
|
WARMER_ACTIVE: boolean;
|
|
27
27
|
constructor(config: {
|
|
28
28
|
prepareRequest: (params: AdapterParams[], context: AdapterContext<CustomSettings>) => AxiosRequestConfig<ProviderRequestBody>;
|
|
29
|
-
parseResponse: (res: AxiosResponse<ProviderResponseBody>, context: AdapterContext<CustomSettings>) => ProviderResult<AdapterParams>[];
|
|
29
|
+
parseResponse: (params: AdapterParams[], res: AxiosResponse<ProviderResponseBody>, context: AdapterContext<CustomSettings>) => ProviderResult<AdapterParams>[];
|
|
30
30
|
});
|
|
31
31
|
initialize(dependencies: AdapterDependencies): Promise<void>;
|
|
32
32
|
hasBeenSetUp(req: AdapterRequest<AdapterParams>): Promise<boolean>;
|
|
@@ -83,7 +83,7 @@ class BatchWarmingTransport {
|
|
|
83
83
|
logger.trace('Sending request to data provider...');
|
|
84
84
|
const providerResponse = await (0, util_2.axiosRequest)(request, context.adapterConfig);
|
|
85
85
|
logger.debug(`Got response from provider, parsing (raw body: ${providerResponse.data})`);
|
|
86
|
-
const results = this.config.parseResponse(providerResponse, context);
|
|
86
|
+
const results = this.config.parseResponse(entries, providerResponse, context);
|
|
87
87
|
const adapterResponses = (0, _1.buildCacheEntriesFromResults)(results, context);
|
|
88
88
|
logger.debug('Setting adapter responses in cache');
|
|
89
89
|
await this.cache.setMany(adapterResponses, context.adapterConfig.CACHE_MAX_AGE);
|
package/transports/index.d.ts
CHANGED
|
@@ -60,7 +60,9 @@ export interface Transport<Params, Result, CustomSettings extends SettingsMap> {
|
|
|
60
60
|
* @param context - context for the Adapter
|
|
61
61
|
* @returns a list of CacheEntries of AdapterResponses
|
|
62
62
|
*/
|
|
63
|
-
export declare const buildCacheEntriesFromResults: <Params, CustomSettings extends SettingsMap>(results: ProviderResult<Params>[], context: AdapterContext<CustomSettings>) => CacheEntry<AdapterResponse<
|
|
63
|
+
export declare const buildCacheEntriesFromResults: <Params, CustomSettings extends SettingsMap>(results: ProviderResult<Params>[], context: AdapterContext<CustomSettings>) => CacheEntry<AdapterResponse<{
|
|
64
|
+
result: unknown;
|
|
65
|
+
}>>[];
|
|
64
66
|
/**
|
|
65
67
|
* Takes an Adapter, its configuration, and its dependencies, and it creates an express middleware
|
|
66
68
|
* that will pass along the AdapterRequest to the appropriate Transport (acc. to the endpoint in the req.)
|
package/transports/index.js
CHANGED
|
@@ -34,7 +34,9 @@ const buildCacheEntriesFromResults = (results, context) => results.map((r) => {
|
|
|
34
34
|
value: {
|
|
35
35
|
result: r.value,
|
|
36
36
|
statusCode: 200,
|
|
37
|
-
data:
|
|
37
|
+
data: {
|
|
38
|
+
result: r.value,
|
|
39
|
+
},
|
|
38
40
|
},
|
|
39
41
|
};
|
|
40
42
|
if (context.adapterConfig.METRICS_ENABLED &&
|
package/transports/metrics.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as client from 'prom-client';
|
|
2
2
|
import { AdapterContext } from '../adapter';
|
|
3
3
|
import { SettingsMap } from '../config';
|
|
4
|
-
export declare const dataProviderMetricsLabel: (providerStatusCode?: number
|
|
4
|
+
export declare const dataProviderMetricsLabel: (providerStatusCode?: number, method?: string) => {
|
|
5
5
|
provider_status_code: number | undefined;
|
|
6
6
|
method: string;
|
|
7
7
|
};
|
package/util/request.d.ts
CHANGED
|
@@ -51,7 +51,6 @@ export declare type AdapterResponse<T = unknown> = {
|
|
|
51
51
|
result: unknown;
|
|
52
52
|
maxAge?: number;
|
|
53
53
|
meta?: AdapterRequestMeta;
|
|
54
|
-
providerStatusCode?: number;
|
|
55
54
|
};
|
|
56
55
|
export declare type Middleware = ((req: AdapterRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => FastifyReply | void) | ((req: AdapterRequest, reply: FastifyReply) => Promise<FastifyReply | void>);
|
|
57
56
|
export declare type AdapterMiddlewareBuilder = (adapter: Adapter) => Middleware;
|
package/validation/index.js
CHANGED
|
@@ -39,7 +39,6 @@ const validatorMiddleware = (adapter) => (req, reply, done) => {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
// Validate data using validator from v2
|
|
42
|
-
// TODO: See if we want to change this whole thing
|
|
43
42
|
const validator = new validator_1.Validator(req.body, endpoint.inputParameters);
|
|
44
43
|
req.requestContext = {
|
|
45
44
|
id: req.body.id || '1',
|
|
@@ -76,7 +75,10 @@ const validatorMiddleware = (adapter) => (req, reply, done) => {
|
|
|
76
75
|
exports.validatorMiddleware = validatorMiddleware;
|
|
77
76
|
const errorCatchingMiddleware = (err, req, res) => {
|
|
78
77
|
// Add adapter or generic error to request meta for metrics use
|
|
79
|
-
|
|
78
|
+
// There's a chance we have no request context if there was an error during input validation
|
|
79
|
+
if (req.requestContext) {
|
|
80
|
+
req.requestContext.meta = { ...req.requestContext?.meta, error: err };
|
|
81
|
+
}
|
|
80
82
|
if (err instanceof error_1.AdapterError) {
|
|
81
83
|
// We want to log these as warn, because although they are to be expected, NOPs should
|
|
82
84
|
// Only use "correct" job specs and therefore not hit adapters with invalid requests.
|