@chainlink/external-adapter-framework 0.0.6 → 0.0.7

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.
Files changed (161) hide show
  1. package/dist/adapter.js +60 -0
  2. package/dist/background-executor.js +45 -0
  3. package/dist/cache/factory.js +57 -0
  4. package/dist/cache/index.js +163 -0
  5. package/dist/cache/local.js +83 -0
  6. package/dist/cache/metrics.js +114 -0
  7. package/dist/cache/redis.js +100 -0
  8. package/dist/config/index.js +364 -0
  9. package/dist/config/provider-limits.js +75 -0
  10. package/dist/examples/coingecko/batch-warming.js +52 -0
  11. package/dist/examples/coingecko/index.js +10 -0
  12. package/dist/examples/coingecko/rest.js +50 -0
  13. package/dist/examples/ncfx/config/index.js +15 -0
  14. package/dist/examples/ncfx/index.js +10 -0
  15. package/dist/examples/ncfx/websocket.js +72 -0
  16. package/dist/index.js +89 -0
  17. package/dist/metrics/constants.js +25 -0
  18. package/dist/metrics/index.js +76 -0
  19. package/dist/metrics/util.js +9 -0
  20. package/dist/rate-limiting/factory.js +33 -0
  21. package/dist/rate-limiting/index.js +36 -0
  22. package/dist/rate-limiting/metrics.js +32 -0
  23. package/dist/rate-limiting/nop-limiter.js +15 -0
  24. package/dist/rate-limiting/simple-counting.js +61 -0
  25. package/dist/src/adapter.js +112 -0
  26. package/dist/src/background-executor.js +45 -0
  27. package/dist/src/cache/factory.js +57 -0
  28. package/dist/src/cache/index.js +165 -0
  29. package/dist/src/cache/local.js +83 -0
  30. package/dist/src/cache/metrics.js +114 -0
  31. package/dist/src/cache/redis.js +100 -0
  32. package/dist/src/config/index.js +366 -0
  33. package/dist/src/config/provider-limits.js +75 -0
  34. package/dist/src/examples/bank-frick/accounts.js +191 -0
  35. package/dist/src/examples/bank-frick/config/index.js +45 -0
  36. package/dist/src/examples/bank-frick/index.js +14 -0
  37. package/dist/src/examples/bank-frick/util.js +39 -0
  38. package/dist/src/examples/coingecko/batch-warming.js +52 -0
  39. package/dist/src/examples/coingecko/index.js +10 -0
  40. package/dist/src/examples/coingecko/rest.js +50 -0
  41. package/dist/src/examples/ncfx/config/index.js +15 -0
  42. package/dist/src/examples/ncfx/index.js +10 -0
  43. package/dist/src/examples/ncfx/websocket.js +72 -0
  44. package/dist/src/index.js +89 -0
  45. package/dist/src/metrics/constants.js +25 -0
  46. package/dist/src/metrics/index.js +76 -0
  47. package/dist/src/metrics/util.js +9 -0
  48. package/dist/src/rate-limiting/background/fixed-frequency.js +37 -0
  49. package/dist/src/rate-limiting/index.js +63 -0
  50. package/dist/src/rate-limiting/metrics.js +32 -0
  51. package/dist/src/rate-limiting/request/simple-counting.js +62 -0
  52. package/dist/src/test.js +6 -0
  53. package/dist/src/transports/batch-warming.js +55 -0
  54. package/dist/src/transports/index.js +85 -0
  55. package/dist/src/transports/metrics.js +119 -0
  56. package/dist/src/transports/rest.js +93 -0
  57. package/dist/src/transports/util.js +85 -0
  58. package/dist/src/transports/websocket.js +175 -0
  59. package/dist/src/util/expiring-sorted-set.js +47 -0
  60. package/dist/src/util/index.js +35 -0
  61. package/dist/src/util/logger.js +62 -0
  62. package/dist/src/util/request.js +2 -0
  63. package/dist/src/validation/error.js +41 -0
  64. package/dist/src/validation/index.js +84 -0
  65. package/dist/src/validation/input-params.js +30 -0
  66. package/dist/src/validation/override-functions.js +40 -0
  67. package/dist/src/validation/preset-tokens.json +23 -0
  68. package/dist/src/validation/validator.js +303 -0
  69. package/dist/test.js +6 -0
  70. package/dist/transports/batch-warming.js +57 -0
  71. package/dist/transports/index.js +76 -0
  72. package/dist/transports/metrics.js +133 -0
  73. package/dist/transports/rest.js +91 -0
  74. package/dist/transports/util.js +85 -0
  75. package/dist/transports/websocket.js +171 -0
  76. package/dist/util/expiring-sorted-set.js +47 -0
  77. package/dist/util/index.js +35 -0
  78. package/dist/util/logger.js +62 -0
  79. package/dist/util/request.js +2 -0
  80. package/dist/validation/error.js +41 -0
  81. package/dist/validation/index.js +82 -0
  82. package/dist/validation/input-params.js +30 -0
  83. package/dist/validation/overrideFunctions.js +42 -0
  84. package/dist/validation/presetTokens.json +23 -0
  85. package/dist/validation/validator.js +303 -0
  86. package/package.json +6 -2
  87. package/.c8rc.json +0 -3
  88. package/.eslintignore +0 -9
  89. package/.eslintrc.js +0 -96
  90. package/.github/README.MD +0 -17
  91. package/.github/actions/setup/action.yaml +0 -13
  92. package/.github/workflows/main.yaml +0 -39
  93. package/.github/workflows/publish.yaml +0 -17
  94. package/.prettierignore +0 -13
  95. package/.yarnrc +0 -0
  96. package/docker-compose.yaml +0 -35
  97. package/src/adapter.ts +0 -236
  98. package/src/background-executor.ts +0 -53
  99. package/src/cache/factory.ts +0 -28
  100. package/src/cache/index.ts +0 -236
  101. package/src/cache/local.ts +0 -73
  102. package/src/cache/metrics.ts +0 -112
  103. package/src/cache/redis.ts +0 -93
  104. package/src/config/index.ts +0 -501
  105. package/src/config/provider-limits.ts +0 -130
  106. package/src/examples/coingecko/batch-warming.ts +0 -79
  107. package/src/examples/coingecko/index.ts +0 -9
  108. package/src/examples/coingecko/rest.ts +0 -77
  109. package/src/examples/ncfx/config/index.ts +0 -12
  110. package/src/examples/ncfx/index.ts +0 -9
  111. package/src/examples/ncfx/websocket.ts +0 -100
  112. package/src/index.ts +0 -106
  113. package/src/metrics/constants.ts +0 -23
  114. package/src/metrics/index.ts +0 -116
  115. package/src/metrics/util.ts +0 -11
  116. package/src/rate-limiting/background/fixed-frequency.ts +0 -47
  117. package/src/rate-limiting/index.ts +0 -100
  118. package/src/rate-limiting/metrics.ts +0 -18
  119. package/src/rate-limiting/request/simple-counting.ts +0 -76
  120. package/src/test.ts +0 -5
  121. package/src/transports/batch-warming.ts +0 -121
  122. package/src/transports/index.ts +0 -173
  123. package/src/transports/metrics.ts +0 -95
  124. package/src/transports/rest.ts +0 -161
  125. package/src/transports/util.ts +0 -63
  126. package/src/transports/websocket.ts +0 -238
  127. package/src/util/expiring-sorted-set.ts +0 -52
  128. package/src/util/index.ts +0 -20
  129. package/src/util/logger.ts +0 -69
  130. package/src/util/request.ts +0 -115
  131. package/src/validation/error.ts +0 -116
  132. package/src/validation/index.ts +0 -101
  133. package/src/validation/input-params.ts +0 -45
  134. package/src/validation/override-functions.ts +0 -44
  135. package/src/validation/preset-tokens.json +0 -23
  136. package/src/validation/validator.ts +0 -384
  137. package/test/adapter.test.ts +0 -27
  138. package/test/background-executor.test.ts +0 -109
  139. package/test/cache/cache-key.test.ts +0 -96
  140. package/test/cache/helper.ts +0 -101
  141. package/test/cache/local.test.ts +0 -54
  142. package/test/cache/redis.test.ts +0 -89
  143. package/test/correlation.test.ts +0 -114
  144. package/test/index.test.ts +0 -37
  145. package/test/metrics/feed-id.test.ts +0 -33
  146. package/test/metrics/helper.ts +0 -14
  147. package/test/metrics/labels.test.ts +0 -36
  148. package/test/metrics/metrics.test.ts +0 -267
  149. package/test/metrics/redis-metrics.test.ts +0 -113
  150. package/test/metrics/warmer-metrics.test.ts +0 -192
  151. package/test/metrics/ws-metrics.test.ts +0 -225
  152. package/test/rate-limit-config.test.ts +0 -243
  153. package/test/transports/batch.test.ts +0 -465
  154. package/test/transports/rest.test.ts +0 -242
  155. package/test/transports/websocket.test.ts +0 -183
  156. package/test/tsconfig.json +0 -5
  157. package/test/util.ts +0 -76
  158. package/test/validation.test.ts +0 -169
  159. package/test.sh +0 -20
  160. package/tsconfig.json +0 -24
  161. package/typedoc.json +0 -6
@@ -1,63 +0,0 @@
1
- import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
2
- import {
3
- AdapterConnectionError,
4
- AdapterDataProviderError,
5
- AdapterError,
6
- AdapterTimeoutError,
7
- } from '../validation/error'
8
- import * as transportMetrics from './metrics'
9
-
10
- /**
11
- * Performs axios request along with metrics recording and error handling
12
- *
13
- * @param request - axios request config
14
- * @returns axios response for the request
15
- */
16
- export async function axiosRequest<ProviderRequestBody, ProviderResponseBody>(
17
- request: AxiosRequestConfig<ProviderRequestBody>,
18
- ): Promise<AxiosResponse<ProviderResponseBody>> {
19
- const responseTimer = transportMetrics.dataProviderRequestDurationSeconds.startTimer()
20
- let providerResponse: AxiosResponse<ProviderResponseBody>
21
- try {
22
- providerResponse = await axios.request<ProviderResponseBody>(request)
23
- } catch (e: unknown) {
24
- const error = e as AxiosError
25
- // Request error
26
- let providerStatusCode: number | undefined
27
- let adapterError: AdapterError
28
- if (error.code === 'ECONNABORTED') {
29
- adapterError = new AdapterTimeoutError({})
30
- providerStatusCode = error?.response?.status ?? 504
31
- adapterError.name = 'Data Provider Request Timeout error'
32
- } else if (error?.response?.status) {
33
- adapterError = new AdapterDataProviderError({})
34
- providerStatusCode = error?.response?.status
35
- } else {
36
- adapterError = new AdapterConnectionError({})
37
- providerStatusCode = 0 // 0 -> connection error
38
- }
39
- // Record count of failed data provider request
40
- transportMetrics.dataProviderRequests
41
- .labels(transportMetrics.dataProviderMetricsLabel(providerStatusCode, request.method))
42
- .inc()
43
-
44
- adapterError.statusCode = 200
45
- adapterError.providerStatusCode = providerStatusCode
46
- adapterError.message = error?.message
47
- adapterError.cause = error
48
- adapterError.errorResponse = error?.response?.data
49
- adapterError.url = request.url
50
-
51
- throw adapterError
52
- } finally {
53
- // Record time taken for data provider request for success or failure
54
- responseTimer()
55
- }
56
-
57
- // Record count of successful data provider requests
58
- transportMetrics.dataProviderRequests
59
- .labels(transportMetrics.dataProviderMetricsLabel(providerResponse.status, request.method))
60
- .inc()
61
-
62
- return providerResponse
63
- }
@@ -1,238 +0,0 @@
1
- import WebSocket from 'ws'
2
- import { Cache } from '../cache'
3
- import { SettingsMap } from '../config'
4
- import { BackgroundExecuteRateLimiter } from '../rate-limiting'
5
- import { ExpiringSortedSet, makeLogger } from '../util'
6
- import { AdapterRequest, ProviderResult } from '../util/request'
7
- import { AdapterContext, AdapterDependencies, Transport, buildCacheEntriesFromResults } from './'
8
- import * as transportMetrics from './metrics'
9
-
10
- // TODO: Config
11
- export const DEFAULT_WS_TTL = 10000
12
-
13
- const logger = makeLogger('WebSocketTransport')
14
-
15
- type WebSocketClass = new (url: string, protocols?: string | string[] | undefined) => WebSocket
16
-
17
- export class WebSocketClassProvider {
18
- static ctor: WebSocketClass = WebSocket
19
-
20
- static set(ctor: WebSocketClass): void {
21
- this.ctor = ctor
22
- }
23
-
24
- static get(): WebSocketClass {
25
- return this.ctor
26
- }
27
- }
28
-
29
- /**
30
- * Config object that is provided to the WebSocketTransport constructor.
31
- */
32
- export interface WebSocketTransportConfig<
33
- AdapterParams,
34
- ProviderDataMessage,
35
- CustomSettings extends SettingsMap,
36
- > {
37
- /** Endpoint to which to open the WS connection*/
38
- url: string
39
-
40
- /** Map of handlers for different WS lifecycle events */
41
- handlers: {
42
- /**
43
- * Handles when the WS is successfully opened
44
- *
45
- * @param wsConnection - the WebSocket with an established connection
46
- * @returns an empty Promise, or void
47
- */
48
- open: (wsConnection: WebSocket, context: AdapterContext<CustomSettings>) => Promise<void> | void
49
-
50
- /**
51
- * Handles when the WS receives a message
52
- *
53
- * @param message - the message received by the WS
54
- * @param context - the background context for the Adapter
55
- * @returns a list of cache entries of adapter responses to set in the cache
56
- */
57
- message: (
58
- message: ProviderDataMessage,
59
- context: AdapterContext<CustomSettings>,
60
- ) => ProviderResult<AdapterParams>[]
61
- }
62
-
63
- /** Map of "builders", functions that will be used to prepare specific WS messages */
64
- builders: {
65
- /**
66
- * Builds a WS message that will be sent to subscribe to a specific feed
67
- *
68
- * @param params - the body of the adapter request
69
- * @returns the WS message (can be any type as long as the [[WebSocket]] doesn't complain)
70
- */
71
- subscribeMessage: (params: AdapterParams) => unknown
72
-
73
- /**
74
- * Builds a WS message that will be sent to unsubscribe to a specific feed
75
- *
76
- * @param params - the body of the adapter request
77
- * @returns the WS message (can be any type as long as the [[WebSocket]] doesn't complain)
78
- */
79
- unsubscribeMessage: (params: AdapterParams) => unknown
80
- }
81
- }
82
-
83
- /**
84
- * Transport implementation that takes incoming requests, adds them to an [[ExpiringSortedSet]] and,
85
- * through a WebSocket connection, subscribes to the relevant feeds to populate the cache.
86
- *
87
- * @typeParam AdapterParams - interface for the adapter request body
88
- * @typeParam ProviderDataMessage - interface for a WS message containing processable data (i.e. not part of open/close/login/etc)
89
- */
90
- export class WebSocketTransport<
91
- AdapterParams,
92
- ProviderDataMessage,
93
- CustomSettings extends SettingsMap,
94
- > implements Transport<AdapterParams, null, CustomSettings>
95
- {
96
- cache!: Cache
97
- rateLimiter!: BackgroundExecuteRateLimiter
98
-
99
- // The double sets serve to create a simple polling mechanism instead of needing a subscription
100
-
101
- // This one would be either local, redis, etc
102
- expiringSortedSet = new ExpiringSortedSet<AdapterParams>() // TODO: Move to dependencies, inject
103
-
104
- // This one would not; this is always local state
105
- localSubscriptions: AdapterParams[] = []
106
-
107
- wsConnection!: WebSocket
108
-
109
- constructor(
110
- private config: WebSocketTransportConfig<AdapterParams, ProviderDataMessage, CustomSettings>,
111
- ) {}
112
-
113
- async initialize(dependencies: AdapterDependencies): Promise<void> {
114
- this.cache = dependencies.cache
115
- this.rateLimiter = dependencies.backgroundExecuteRateLimiter
116
- }
117
-
118
- async hasBeenSetUp(req: AdapterRequest<AdapterParams>): Promise<boolean> {
119
- return !!this.expiringSortedSet.get(req.requestContext.cacheKey)
120
- }
121
-
122
- async setup(req: AdapterRequest<AdapterParams>): Promise<void> {
123
- logger.debug(
124
- `Adding entry to subscription set: [${req.requestContext.cacheKey}] = ${req.requestContext.data}`,
125
- )
126
- this.expiringSortedSet.add(req.requestContext.cacheKey, req.requestContext.data, DEFAULT_WS_TTL)
127
- }
128
-
129
- // TODO: Maybe we don't do this, and leave the preparation on the adapter's side?
130
- // TODO: Maybe we store adapter params pre-prepared? That would be more efficient
131
- // Assuming always JSON payloads for now, makes it all cleaner
132
- serializeMessage(payload: unknown): string {
133
- return typeof payload === 'string' ? payload : JSON.stringify(payload)
134
- }
135
- deserializeMessage(data: WebSocket.Data): ProviderDataMessage {
136
- return JSON.parse(data.toString()) as ProviderDataMessage
137
- }
138
-
139
- establishWsConnection(context: AdapterContext<CustomSettings>) {
140
- return new Promise((resolve) => {
141
- const ctor = WebSocketClassProvider.get()
142
- this.wsConnection = new ctor(this.config.url)
143
- this.wsConnection.addEventListener('open', async (event: WebSocket.Event) => {
144
- logger.debug(`Opened websocket connection.`)
145
- await this.config.handlers.open(this.wsConnection, context)
146
- logger.debug('Successfully executed connection opened handler')
147
- // Record active ws connections by incrementing count on open
148
- // Using URL in label since connection_key is removed from v3
149
- transportMetrics.wsConnectionActive.inc()
150
- resolve(true)
151
- })
152
- this.wsConnection.addEventListener('message', async (event: WebSocket.MessageEvent) => {
153
- // TODO: Assuming JSON always, maybe use BSON also?
154
- const parsed = this.deserializeMessage(event.data)
155
- logger.trace(`Got ws message: ${parsed}`)
156
- const results = this.config.handlers.message(parsed, context)
157
- if (Array.isArray(results)) {
158
- const responses = buildCacheEntriesFromResults(results, context)
159
- logger.trace(`Writing ${responses.length} responses to cache`)
160
- await this.cache.setMany(responses, context.adapterConfig.CACHE_MAX_AGE)
161
- }
162
- })
163
- this.wsConnection.addEventListener('error', async (event: WebSocket.ErrorEvent) => {
164
- // Record connection error count
165
- transportMetrics.wsConnectionErrors
166
- .labels(transportMetrics.connectionErrorLabels(event.message))
167
- .inc()
168
- })
169
- this.wsConnection.addEventListener('close', (event: WebSocket.CloseEvent) => {
170
- logger.debug(
171
- `Closed websocket connection. Code: ${event.code} ; reason: ${event.reason?.toString()}`,
172
- )
173
- // Record active ws connections by decrementing count on close
174
- // Using URL in label since connection_key is removed from v3
175
- transportMetrics.wsConnectionActive.dec()
176
- })
177
- })
178
- }
179
-
180
- // Unlike cache warming, this execute will manage subscriptions
181
- async backgroundExecute(context: AdapterContext<CustomSettings>): Promise<number> {
182
- logger.debug('Starting background execute, getting subscriptions from sorted set')
183
- const desiredSubs = this.expiringSortedSet.getAll()
184
-
185
- logger.debug('Generating delta (subscribes & unsubscribes)')
186
- // TODO: More efficient algorithm, this is really easy to read, but high(er) time complexity
187
- const subscribeParams = desiredSubs.filter((s) => !this.localSubscriptions.includes(s))
188
- const subscribes = subscribeParams
189
- .map(this.config.builders.subscribeMessage)
190
- .map(this.serializeMessage)
191
-
192
- const unsubscribeParams = this.localSubscriptions.filter((s) => !desiredSubs.includes(s))
193
- const unsubscribes = unsubscribeParams
194
- .map(this.config.builders.unsubscribeMessage)
195
- .map(this.serializeMessage)
196
-
197
- logger.debug(`${subscribes.length} new subscriptions; ${unsubscribes.length} to unsubscribe`)
198
- if (subscribes.length) {
199
- logger.trace(`Will subscribe to: ${subscribes}`)
200
- }
201
- if (unsubscribes.length) {
202
- logger.trace(`Will unsubscribe to: ${unsubscribes}`)
203
- }
204
-
205
- // New subs && no connection -> connect -> add subs
206
- // No new subs && no connection -> skip
207
- // New subs && connection -> add subs
208
- // No new subs && connection -> unsubs only
209
-
210
- if (!subscribes.length && !this.wsConnection) {
211
- logger.debug('No entries in subscription set and no established connection, skipping')
212
- return this.rateLimiter.msUntilNextExecution(context.adapterEndpoint.name)
213
- }
214
-
215
- if (!this.wsConnection && subscribes.length) {
216
- logger.debug('No established connection and new subscriptions available, connecting to WS')
217
- await this.establishWsConnection(context)
218
- }
219
-
220
- // TODO: Close connection at some point?
221
-
222
- logger.debug('Sending subs/unsubs if there are any')
223
- const messages = unsubscribes.concat(subscribes)
224
- for (const message of messages) {
225
- logger.trace(`Sending message: ${JSON.stringify(message)}`)
226
- this.wsConnection.send(message)
227
- }
228
-
229
- // Record WS message and subscription metrics
230
- transportMetrics.recordWsMessageMetrics(context as AdapterContext, subscribeParams, unsubscribeParams)
231
-
232
- logger.debug('Setting local state to cache value')
233
- this.localSubscriptions = desiredSubs
234
-
235
- logger.debug('Background execute complete')
236
- return this.rateLimiter.msUntilNextExecution(context.adapterEndpoint.name)
237
- }
238
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * An object describing an entry in the expiring sorted set.
3
- * @typeParam T - the type of the entry's value
4
- */
5
- interface ExpiringSortedSetEntry<T> {
6
- value: T
7
- expirationTimestamp: number
8
- }
9
-
10
- /**
11
- * This class implements a set of unique items, each of which has an expiration timestamp.
12
- * On reads, items that have expired will be deleted from the set and not returned.
13
- *
14
- * @typeParam T - the type of the set entries' values
15
- */
16
- export class ExpiringSortedSet<T> {
17
- map = new Map<string, ExpiringSortedSetEntry<T>>()
18
-
19
- add(key: string, value: T, ttl: number) {
20
- this.map.set(key, {
21
- value,
22
- expirationTimestamp: Date.now() + ttl,
23
- })
24
- }
25
-
26
- get(key: string) {
27
- const entry = this.map.get(key)
28
- if (!entry) {
29
- return
30
- } else if (entry.expirationTimestamp < Date.now()) {
31
- return entry.value
32
- } else {
33
- this.map.delete(key)
34
- }
35
- }
36
-
37
- getAll(): T[] {
38
- const results: T[] = []
39
- const now = Date.now()
40
-
41
- // Since we're iterating, might as well prune here
42
- for (const [key, entry] of this.map.entries()) {
43
- if (entry.expirationTimestamp < now) {
44
- this.map.delete(key) // In theory, this shouldn't happen frequently for feeds
45
- } else {
46
- results.push(entry.value)
47
- }
48
- }
49
-
50
- return results
51
- }
52
- }
package/src/util/index.ts DELETED
@@ -1,20 +0,0 @@
1
- export * from './request'
2
- export * from './logger'
3
- export * from './expiring-sorted-set'
4
-
5
- /**
6
- * Sleeps for the provided number of milliseconds
7
- * @param ms - The number of milliseconds to sleep for
8
- * @returns a Promise that resolves once the specified time passes
9
- */
10
- export const sleep = (ms: number): Promise<void> => {
11
- return new Promise((resolve) => {
12
- setTimeout(resolve, ms)
13
- })
14
- }
15
-
16
- export const isObject = (o: unknown): boolean =>
17
- o !== null && typeof o === 'object' && Array.isArray(o) === false
18
-
19
- export const isArray = (o: unknown): boolean =>
20
- o !== null && typeof o === 'object' && Array.isArray(o)
@@ -1,69 +0,0 @@
1
- import pino from 'pino'
2
- import { BaseSettings } from '../config'
3
- import { AdapterRequest } from './request'
4
- import { FastifyReply, HookHandlerDoneFunction } from 'fastify'
5
- import { randomUUID } from 'crypto'
6
- import { AsyncLocalStorage } from 'node:async_hooks'
7
-
8
- export const asyncLocalStorage = new AsyncLocalStorage()
9
-
10
- export type Store = {
11
- correlationId: string
12
- }
13
-
14
- const debugTransport = {
15
- target: 'pino-pretty',
16
- options: {
17
- levelFirst: true,
18
- levelLabel: 'level',
19
- ignore: 'layer,pid,hostname',
20
- messageFormat: '[{correlationId}][{layer}] {msg}',
21
- translateTime: 'yyyy-mm-dd HH:MM:ss.l',
22
- },
23
- }
24
-
25
- // Base logger, shouldn't be used because we want layers to be specified
26
- const baseLogger = pino({
27
- level: process.env['LOG_LEVEL']?.toLowerCase() || BaseSettings.LOG_LEVEL.default,
28
- mixin() {
29
- if (process.env['CORRELATION_ID_ENABLED'] === 'true') {
30
- const store = asyncLocalStorage.getStore() as Store
31
- if (store) {
32
- return store
33
- }
34
- }
35
- return {}
36
- },
37
- transport: process.env['DEBUG'] === 'true' ? debugTransport : undefined,
38
- })
39
-
40
- /**
41
- * Instead of using a global logger instance, we want to force using a child logger
42
- * with a specific layer set in it, so that we can filter logs by where they're output from.
43
- *
44
- * Details on what each log level represents:
45
- * "trace": Forensic debugging of issues on a local machine.
46
- * "debug": Detailed logging level to get more context from users on their environments.
47
- * "info": High-level informational messages, to describe at a glance the high level state of the system.
48
- * "warn": A mild error occurred that might require non-urgent action.
49
- * "error": An unexpected error occurred during the regular operation of a well-maintained EA.
50
- * "fatal": The EA encountered an unrecoverable problem and had to exit.
51
- *
52
- * Full reference this is based on can be found at
53
- * https://github.com/smartcontractkit/documentation/blob/main/docs/Node%20Operators/configuration-variables.md#log_level
54
- *
55
- * @param layer - the layer name to include in the logs (e.g. "SomeMiddleware", "RedisCache", etc.)
56
- * @returns a layer specific logger
57
- */
58
- export const makeLogger = (layer: string) => baseLogger.child({ layer })
59
-
60
- export const loggingContextMiddleware = (
61
- req: AdapterRequest,
62
- res: FastifyReply,
63
- done: HookHandlerDoneFunction,
64
- ) => {
65
- const correlationId = req.headers['x-correlation-id'] || randomUUID()
66
- asyncLocalStorage.run({ correlationId: correlationId }, () => {
67
- done()
68
- })
69
- }
@@ -1,115 +0,0 @@
1
- import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify'
2
- import { InitializedAdapter } from '../adapter'
3
- import { AdapterError } from '../validation/error'
4
- declare module 'fastify' {
5
- // eslint-disable-next-line no-shadow
6
- export interface FastifyRequest {
7
- requestContext: AdapterRequestContext
8
- }
9
- }
10
-
11
- export interface AdapterRequestBody<T = AdapterRequestData> {
12
- endpoint?: string
13
- data: T
14
- id?: string
15
- }
16
-
17
- export type AdapterRequestContext<T = AdapterRequestData> = {
18
- id: string
19
- endpointName: string
20
- cacheKey: string
21
- data: T // AdapterRequestData
22
- meta?: AdapterRequestMeta
23
- // MetricsMeta?: AdapterMetricsMeta
24
- // Debug?: AdapterDebug
25
- // RateLimitMaxAge?: number
26
- }
27
-
28
- export type AdapterRouteGeneric<T = AdapterRequestData> = {
29
- Body: AdapterRequestBody<T>
30
- }
31
-
32
- export type AdapterRequest<T = AdapterRequestData> = FastifyRequest<AdapterRouteGeneric<T>> & {
33
- // TODO: Uncomment line below & fix compile issues
34
- // body: never // So the user does not access the raw request data and uses the Validated one instead
35
- requestContext: AdapterRequestContext<T>
36
- }
37
-
38
- // Export type AdapterRequestMeta = {
39
- // AvailableFunds?: number
40
- // EligibleToSubmit?: boolean
41
- // LatestAnswer?: number
42
- // OracleCount?: number
43
- // PaymentAmount?: number
44
- // ReportableRoundID?: number
45
- // StartedAt?: number
46
- // Timeout?: number
47
- // }
48
-
49
- // Export type AdapterDebug = {
50
- // Ws?: boolean
51
- // Warmer?: boolean
52
- // CacheHit?: boolean
53
- // Staleness?: number
54
- // Performance?: number
55
- // ProviderCost?: number
56
- // BatchablePropertyPath?: cacheWarmer.reducer.BatchableProperty[]
57
- // NormalizedRequest?: Record<string, unknown>
58
- // }
59
-
60
- /**
61
- * Meta info that pertains to exposing metrics
62
- */
63
- export interface AdapterRequestMeta {
64
- metrics?: AdapterMetricsMeta
65
- error?: AdapterError | Error
66
- }
67
-
68
- /**
69
- * Meta info that pertains to exposing metrics
70
- */
71
- export interface AdapterMetricsMeta {
72
- feedId?: string
73
- cacheHit?: boolean
74
- }
75
-
76
- export type AdapterRequestData = Record<string, unknown>
77
-
78
- // Export interface AdapterRequest<T = AdapterRequestData> extends Request {
79
- // AdapterContext: {
80
- // Id: string
81
- // Endpoint: string
82
- // Data: T //AdapterRequestData
83
- // CacheKey: string
84
- // // meta?: AdapterRequestMeta
85
- // // metricsMeta?: AdapterMetricsMeta
86
- // // debug?: AdapterDebug
87
- // // rateLimitMaxAge?: number
88
- // }
89
- // }
90
-
91
- export interface ProviderResult<Params> {
92
- params: Params
93
- value: unknown
94
- }
95
-
96
- export type AdapterResponse<T = unknown> = {
97
- // JobRunID: string
98
- statusCode: number
99
- data: T // Response data, holds "result" for Flux Monitor. Correct way.
100
- result: unknown // Result for OCR
101
- maxAge?: number // TODO: Replace with new telemetry structure
102
- meta?: AdapterRequestMeta
103
- // Debug?: AdapterDebug
104
- providerStatusCode?: number
105
- }
106
-
107
- export type Middleware =
108
- | ((
109
- req: AdapterRequest,
110
- reply: FastifyReply,
111
- done: HookHandlerDoneFunction,
112
- ) => FastifyReply | void)
113
- | ((req: AdapterRequest, reply: FastifyReply) => Promise<FastifyReply | void>)
114
-
115
- export type AdapterMiddlewareBuilder = (adapter: InitializedAdapter) => Middleware
@@ -1,116 +0,0 @@
1
- import { HttpRequestType } from "../metrics/constants"
2
-
3
- type ErrorBasic = {
4
- name: string
5
- message: string
6
- }
7
- type ErrorFull = ErrorBasic & {
8
- stack: string
9
- cause: string
10
- }
11
-
12
- export type AdapterErrorResponse = {
13
- jobRunID: string
14
- status: string
15
- statusCode: number
16
- providerStatusCode?: number
17
- error: ErrorBasic | ErrorFull
18
- }
19
-
20
- export class AdapterError extends Error {
21
- jobRunID: string
22
- status: string
23
- statusCode: number
24
- cause: any
25
- url?: string
26
- errorResponse: any
27
- feedID?: string
28
- providerStatusCode?: number
29
- metricsLabel?: HttpRequestType
30
-
31
- override name: string
32
- override message: string
33
-
34
- constructor({
35
- jobRunID = '1',
36
- status = 'errored',
37
- statusCode = 500,
38
- name = 'AdapterError',
39
- message = 'An error occurred.',
40
- cause,
41
- url,
42
- errorResponse,
43
- feedID,
44
- providerStatusCode,
45
- metricsLabel = HttpRequestType.ADAPTER_ERROR
46
- }: Partial<AdapterError>) {
47
- super(message)
48
-
49
- this.jobRunID = jobRunID
50
- this.status = status
51
- this.statusCode = statusCode
52
- this.name = name
53
- this.message = message
54
- this.cause = cause
55
- if (url) {
56
- this.url = url
57
- }
58
- if (feedID) {
59
- this.feedID = feedID
60
- }
61
- this.errorResponse = errorResponse
62
- this.providerStatusCode = providerStatusCode
63
- this.metricsLabel = metricsLabel
64
- }
65
-
66
- toJSONResponse(): AdapterErrorResponse {
67
- const showDebugInfo =
68
- process.env['NODE_ENV'] === 'development' || process.env['DEBUG'] === 'true'
69
- const errorBasic = {
70
- name: this.name,
71
- message: this.message,
72
- url: this.url,
73
- errorResponse: this.errorResponse,
74
- feedID: this.feedID,
75
- }
76
- const errorFull = { ...errorBasic, stack: this.stack, cause: this.cause }
77
- return {
78
- jobRunID: this.jobRunID,
79
- status: this.status,
80
- statusCode: this.statusCode,
81
- providerStatusCode: this.providerStatusCode,
82
- error: showDebugInfo ? errorFull : errorBasic,
83
- }
84
- }
85
- }
86
-
87
- export class AdapterInputError extends AdapterError {
88
- constructor(input: Partial<AdapterError>) {
89
- super({ ...input, metricsLabel: HttpRequestType.INPUT_ERROR })
90
- }
91
- }
92
- export class AdapterRateLimitError extends AdapterError {
93
- constructor(input: Partial<AdapterError>) {
94
- super({ ...input, metricsLabel: HttpRequestType.RATE_LIMIT_ERROR })
95
- }
96
- }
97
- export class AdapterTimeoutError extends AdapterError {
98
- constructor(input: Partial<AdapterError>) {
99
- super({ ...input, metricsLabel: HttpRequestType.TIMEOUT_ERROR })
100
- }
101
- }
102
- export class AdapterDataProviderError extends AdapterError {
103
- constructor(input: Partial<AdapterError>) {
104
- super({ ...input, metricsLabel: HttpRequestType.DP_ERROR })
105
- }
106
- }
107
- export class AdapterConnectionError extends AdapterError {
108
- constructor(input: Partial<AdapterError>) {
109
- super({ ...input, metricsLabel: HttpRequestType.CONNECTION_ERROR })
110
- }
111
- }
112
- export class AdapterCustomError extends AdapterError {
113
- constructor(input: Partial<AdapterError>) {
114
- super({ ...input, metricsLabel: HttpRequestType.CUSTOM_ERROR })
115
- }
116
- }