@exodus/market-history 3.1.2 → 4.0.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.0.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/market-history@4.0.0...@exodus/market-history@4.0.1) (2023-07-05)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **market-history:** publish atoms dir ([#2287](https://github.com/ExodusMovement/exodus-hydra/issues/2287)) ([17349b1](https://github.com/ExodusMovement/exodus-hydra/commit/17349b1a1183598d5ee6033c0bbe7e7b9e56ceaa))
11
+
12
+ ## [4.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/market-history@3.1.2...@exodus/market-history@4.0.0) (2023-07-05)
13
+
14
+ ### ⚠ BREAKING CHANGES
15
+
16
+ - market-history atom (#2160)
17
+
18
+ ### Features
19
+
20
+ - market-history atom ([#2160](https://github.com/ExodusMovement/exodus-hydra/issues/2160)) ([b74364a](https://github.com/ExodusMovement/exodus-hydra/commit/b74364a47e8a51b3eea6b8cc870c731def65726b))
21
+
6
22
  ## [3.1.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/market-history@3.1.1...@exodus/market-history@3.1.2) (2023-06-21)
7
23
 
8
24
  **Note:** Version bump only for package @exodus/market-history
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # @exodus/market-history
2
2
 
3
- module fetches daily and hourly historical prices for assets by interval
4
- returns prices on interval `close`.
3
+ module fetches daily and hourly historical prices for assets by interval.
4
+ Return prices on interval `close`.
5
+
6
+ Public methods:
7
+ `start` - starts monitor
8
+ `update` update prices by granularity (day|hour) manually
5
9
 
6
10
  ## Install
7
11
 
@@ -16,44 +20,40 @@ import createMarketHistoryMonitor from '@exodus/market-history/module'
16
20
 
17
21
  const marketHistoryMonitor = createMarketHistoryMonitor.factory({
18
22
  assetsModule,
19
- currencyAtom,
20
23
  storage: marketHistoryStorage,
24
+ currencyAtom,
21
25
  pricingClient,
22
26
  getCacheKey: ({ currency, assetName, granularity }) =>
23
27
  `prices-${currency}-${assetName}-${granularity}`, // optional
24
- remoteConfigRefreshIntervalAtom, // optional
28
+ remoteConfigRefreshIntervalAtom, // optional. stores interval in ms. if defined we compare distance in ms from previous fetch and if it's larger than defined in this atom we ignore cache.
25
29
  clearCacheAtom, // optional. set value if you want to clear cache.
26
30
  remoteConfigClearCacheAtom, // optional. If you want to clear cache set any value in this atom.
31
+ enabledAssetsAtom, // we fetch prices only for enabled assets
32
+ marketHistoryAtom, // data stored here
27
33
  })
28
34
 
29
- // returns market history for all enabled assets
30
- marketHistoryMonitor.on('market-history', ({ currency, granularity, prices }) => {
31
- // `currency` USD
32
- // `granularity` daily|hourly
33
- // prices
34
- // {
35
- // bitcoin: {
36
- // 1663668000000: 90000,
37
- // 1663671600000: 120000,
38
- // 1663675200000: 100000000,
39
- // }
40
- // }
41
- })
42
-
43
- // returns prices when list of enabled changes, only for these assets
44
- marketHistoryMonitor.on('market-history-new-assets', ({ currency, granularity, prices }) => {
45
- // `currency` USD
46
- // `granularity` daily|hourly
47
- // prices
48
- // {
49
- // bitcoin: {
50
- // 1663668000000: 90000,
51
- // 1663671600000: 120000,
52
- // 1663675200000: 100000000,
53
- // }
54
- // }
55
- })
35
+ // stores market history in marketHistoryAtom in following format
36
+ // marketHistoryAtom.get()
37
+ // {
38
+ // data: {
39
+ // USD: {
40
+ // daily: {
41
+ // bitcoin: {
42
+ // 1663668000000: 90000,
43
+ // }
44
+ // }
45
+ // }
46
+ // },
47
+ // changes: {
48
+ // USD: {
49
+ // daily: {
50
+ // bitcoin: {
51
+ // 1663671600000: 9000,
52
+ // }
53
+ // }
54
+ // }
55
+ // }
56
+ // }
56
57
 
57
58
  marketHistoryMonitor.start()
58
- marketHistoryMonitor.sync() // force to emit prices
59
59
  ```
package/atoms/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { createInMemoryAtom } from '@exodus/atoms'
2
+
3
+ export const marketHistoryAtomDefinition = {
4
+ id: 'marketHistoryAtom',
5
+ type: 'atom',
6
+ factory: () => createInMemoryAtom({}),
7
+ dependencies: [],
8
+ }
package/module/index.js CHANGED
@@ -19,6 +19,19 @@ const transformPriceEntries = (entries = []) => {
19
19
  const transformPricesByAssetName = (pricesByAssetName) =>
20
20
  mapValues(pricesByAssetName, (entries) => transformPriceEntries(entries))
21
21
 
22
+ const mergePricesInAtom = ({ prices, atomData, parsedGranularity, currency }) => {
23
+ return {
24
+ ...atomData,
25
+ [currency]: {
26
+ ...atomData[currency],
27
+ [parsedGranularity]: {
28
+ ...atomData[currency]?.[parsedGranularity],
29
+ ...prices,
30
+ },
31
+ },
32
+ }
33
+ }
34
+
22
35
  // when provided cache version from local or remote config is different from stored on device we clear the cache and fetch new prices
23
36
  const _invalidateStorageCache = async ({
24
37
  storage,
@@ -64,7 +77,9 @@ class MarketHistoryMonitor extends ExodusModule {
64
77
  #clearCacheAtom = null
65
78
  #remoteConfigClearCacheAtom = null
66
79
  #runtimeCache = new Map()
80
+ #marketHistoryAtom = null
67
81
  #enabledAssetsAtom = null
82
+ #logger = null
68
83
 
69
84
  constructor({
70
85
  assetsModule,
@@ -76,6 +91,8 @@ class MarketHistoryMonitor extends ExodusModule {
76
91
  clearCacheAtom = createInMemoryAtom({ defaultValue: null }),
77
92
  remoteConfigClearCacheAtom = createInMemoryAtom({ defaultValue: null }),
78
93
  enabledAssetsAtom,
94
+ marketHistoryAtom,
95
+ logger,
79
96
  }) {
80
97
  super({ name: 'MarketHistoryMonitor' })
81
98
 
@@ -88,6 +105,8 @@ class MarketHistoryMonitor extends ExodusModule {
88
105
  this.#remoteConfigClearCacheAtom = remoteConfigClearCacheAtom
89
106
  this.#currencyAtom = currencyAtom
90
107
  this.#enabledAssetsAtom = enabledAssetsAtom
108
+ this.#marketHistoryAtom = marketHistoryAtom
109
+ this.#logger = logger
91
110
  }
92
111
 
93
112
  #started = false
@@ -201,9 +220,17 @@ class MarketHistoryMonitor extends ExodusModule {
201
220
  granularity,
202
221
  assetNames,
203
222
  })
204
- this.emit('market-history', { currency, granularity: parsedGranularity, prices })
223
+
224
+ await this.#marketHistoryAtom.set(async (current) => ({
225
+ data: mergePricesInAtom({
226
+ prices,
227
+ atomData: current?.data ?? {},
228
+ currency,
229
+ parsedGranularity,
230
+ }),
231
+ }))
205
232
  } catch (error) {
206
- console.error(
233
+ this.#logger.error(
207
234
  'MarketHistoryMonitor: Failed to fetch rates',
208
235
  assetNames,
209
236
  currency,
@@ -222,14 +249,13 @@ class MarketHistoryMonitor extends ExodusModule {
222
249
  const currency = this.#currency
223
250
 
224
251
  try {
225
- const prices = await this.#fetch({
252
+ return await this.#fetch({
226
253
  currency,
227
254
  granularity,
228
255
  assetNames,
229
256
  })
230
- this.emit('market-history-new-assets', { currency, granularity: parsedGranularity, prices })
231
257
  } catch (error) {
232
- console.error(
258
+ this.#logger.error(
233
259
  'MarketHistoryMonitor: Failed to fetch rates for assets',
234
260
  assetNames,
235
261
  currency,
@@ -238,34 +264,56 @@ class MarketHistoryMonitor extends ExodusModule {
238
264
  )
239
265
  }
240
266
  }
241
- #updateNewAssets = (assetNames) => {
242
- return Promise.all([
243
- this.#updateAssets('hour', assetNames),
244
- this.#updateAssets('day', assetNames),
245
- ])
246
- }
247
- #syncGranularity = async (granularity) => {
248
- const parsedGranularity = parseGranularity(granularity)
267
+ #updateNewAssets = async (assetNames) => {
249
268
  const currency = this.#currency
250
- const enabledAssets = await this.#enabledAssetsAtom.get()
251
269
 
252
- const cachedPricesByAssetName = Object.fromEntries(
253
- Object.keys(enabledAssets).map((assetName) => {
254
- const key = this.#getCacheKey({ currency, assetName, granularity })
255
- return [assetName, this.#runtimeCache.get(key)]
256
- })
257
- )
270
+ const granularities = ['hour', 'day']
258
271
 
259
- const prices = transformPricesByAssetName(cachedPricesByAssetName)
260
- this.emit('market-history', { currency, granularity: parsedGranularity, prices })
261
- }
272
+ const promises = granularities.map((granularity) => this.#updateAssets(granularity, assetNames))
273
+
274
+ const results = await Promise.all(promises)
275
+ let changes = {}
276
+ const updateChanges = ({ prices, granularity }) => {
277
+ changes = {
278
+ ...changes,
279
+ [currency]: {
280
+ ...changes[currency],
281
+ [granularity]: prices,
282
+ },
283
+ }
284
+ }
262
285
 
263
- sync = async () => {
264
- this.#syncGranularity('day')
265
- this.#syncGranularity('hour')
286
+ granularities.forEach((granularity, index) => {
287
+ const result = results[index]
288
+ if (result) {
289
+ updateChanges({ prices: result, granularity: parseGranularity(granularity) })
290
+ }
291
+ })
292
+
293
+ if (Object.keys(changes).length > 0) {
294
+ await this.#marketHistoryAtom.set((current) => {
295
+ let data = current?.data ?? {}
296
+ granularities.forEach((granularity, index) => {
297
+ const result = results[index]
298
+ if (result) {
299
+ data = mergePricesInAtom({
300
+ prices: result,
301
+ atomData: data,
302
+ currency,
303
+ parsedGranularity: parseGranularity(granularity),
304
+ })
305
+ }
306
+ })
307
+
308
+ return {
309
+ data,
310
+ changes,
311
+ }
312
+ })
313
+ }
266
314
  }
267
315
 
268
- invalidateStorage = async ({ remoteConfigClearCacheVersion }) => {
316
+ #invalidateStorage = async ({ remoteConfigClearCacheVersion }) => {
269
317
  const clearCacheVersion = await this.#clearCacheAtom.get() // don't think we need to observe it considering this runs only on startup and set manually in config
270
318
  return _invalidateStorageCache({
271
319
  storage: this.storage,
@@ -280,7 +328,7 @@ class MarketHistoryMonitor extends ExodusModule {
280
328
  #listenRemoteConfigClearCacheVersionChanges = () => {
281
329
  difference(this.#remoteConfigClearCacheAtom).observe(async ({ current, previous }) => {
282
330
  if (previous !== undefined && current !== previous) {
283
- await this.invalidateStorage({ remoteConfigClearCacheVersion: current })
331
+ await this.#invalidateStorage({ remoteConfigClearCacheVersion: current })
284
332
  await this.#updateAll()
285
333
  }
286
334
  })
@@ -309,14 +357,13 @@ class MarketHistoryMonitor extends ExodusModule {
309
357
 
310
358
  start = async () => {
311
359
  if (this.#started) {
312
- await this.sync()
313
360
  return
314
361
  }
315
362
 
316
363
  this.#started = true
317
364
 
318
365
  const remoteConfigClearCacheVersion = await this.#remoteConfigClearCacheAtom.get()
319
- await this.invalidateStorage({ remoteConfigClearCacheVersion })
366
+ await this.#invalidateStorage({ remoteConfigClearCacheVersion })
320
367
  this.#listenRemoteConfigClearCacheVersionChanges()
321
368
 
322
369
  const combinedAtom = combine({
@@ -364,5 +411,6 @@ export default {
364
411
  'remoteConfigClearCacheAtom',
365
412
  'remoteConfigRefreshIntervalAtom',
366
413
  'enabledAssetsAtom',
414
+ 'marketHistoryAtom',
367
415
  ],
368
416
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@exodus/market-history",
3
- "version": "3.1.2",
4
- "description": "module to fetch historical prices for assets",
5
- "author": "Exodus Movement Inc",
3
+ "version": "4.0.1",
4
+ "description": "Fetches historical prices for assets",
5
+ "author": "Exodus Movement Inc.",
6
6
  "license": "UNLICENSED",
7
7
  "scripts": {
8
8
  "lint": "eslint . --ignore-path ../../.gitignore",
@@ -11,10 +11,11 @@
11
11
  },
12
12
  "files": [
13
13
  "module",
14
+ "atoms",
14
15
  "utils.js",
15
16
  "CHANGELOG.md",
16
17
  "README.md",
17
- "!**/__tests__"
18
+ "!**/__tests__/**"
18
19
  ],
19
20
  "dependencies": {
20
21
  "@exodus/basic-utils": "^2.0.0",
@@ -25,7 +26,7 @@
25
26
  "devDependencies": {
26
27
  "@exodus/assets": "^8.0.88",
27
28
  "@exodus/assets-base": "^8.1.6",
28
- "@exodus/atoms": "^5.0.0",
29
+ "@exodus/atoms": "^5.2.1",
29
30
  "@exodus/storage-memory": "^2.1.0",
30
31
  "eslint": "^8.33.0",
31
32
  "events": "^3.3.0",
@@ -34,5 +35,13 @@
34
35
  "peerDependencies": {
35
36
  "debug": ">= 3.0.0"
36
37
  },
37
- "gitHead": "f0a3d249bf994a6d5c106a106b5e4b2b6c61a6e8"
38
+ "homepage": "https://github.com/ExodusMovement/exodus-hydra/tree/master/modules/crypto-news-monitor",
39
+ "bugs": {
40
+ "url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Crypto-news-monitor"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/ExodusMovement/exodus-hydra.git"
45
+ },
46
+ "gitHead": "07ccaeaebac57e34e8162bae3ecfa2e98c216469"
38
47
  }