@exodus/atoms 4.0.1 → 5.1.0

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/README.md CHANGED
@@ -22,7 +22,6 @@ This library provides helpers for creating atoms from multiple data sources we u
22
22
  | ------------- | --- | ----- | ------- |
23
23
  | Memory | ✅ | ✅ | ✅ |
24
24
  | Storage | ✅ | 🟡 \* | ✅ |
25
- | Fusion | ✅ | ✅ | ✅ |
26
25
  | Remote config | ✅ | ❌ | ✅ |
27
26
  | Local config | ✅ | ✅ | ✅ |
28
27
  | Event emitter | ✅ | ❌ | ✅ |
@@ -35,7 +34,6 @@ This library provides helpers for creating atoms from multiple data sources we u
35
34
  import {
36
35
  createInMemoryAtom,
37
36
  createStorageAtomFactory,
38
- createFusionAtomFactory,
39
37
  createRemoteConfigAtomFactory,
40
38
  fromEventEmitter,
41
39
  } from '@exodus/atoms'
@@ -54,14 +52,6 @@ const acceptedTermsAtom = storageAtomFactory({
54
52
  isSoleWriter: true,
55
53
  })
56
54
 
57
- // Fusion atoms
58
- const fusionAtomFactory = createFusionAtomFactory({ fusion })
59
-
60
- const shareActivityAtom = createFusionAtom({
61
- path: 'shareActivity',
62
- defaultValue: true,
63
- })
64
-
65
55
  // Remote config atoms
66
56
  const createRemoteConfigAtom = createRemoteConfigAtomFactory({ remoteConfig })
67
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/atoms",
3
- "version": "4.0.1",
3
+ "version": "5.1.0",
4
4
  "main": "src/index.js",
5
5
  "author": "Exodus Movement Inc.",
6
6
  "scripts": {
@@ -34,5 +34,5 @@
34
34
  "eslint": "^8.33.0",
35
35
  "jest": "^29.1.2"
36
36
  },
37
- "gitHead": "ac6c0c517dc7ce598f40dedf289d03b3825c9298"
37
+ "gitHead": "da31eb7383fbc80205aef2c85c88ae31807f1146"
38
38
  }
@@ -5,22 +5,17 @@ const withChangeDetection = (listener) => {
5
5
  let currentValue
6
6
  let called = false
7
7
 
8
- return {
9
- listener: async (value) => {
10
- if (called && value === currentValue) return
8
+ return async (value) => {
9
+ if (called && value === currentValue) return
11
10
 
12
- called = true
13
- currentValue = value
11
+ called = true
12
+ currentValue = value
14
13
 
15
- await listener(currentValue)
16
- },
17
- get called() {
18
- return called
19
- },
14
+ await listener(currentValue)
20
15
  }
21
16
  }
22
17
 
23
- const enforceObservableRules = ({ defaultValue, logger, ...atom }) => {
18
+ const enforceObservableRules = ({ defaultValue, getInitialized = () => true, logger, ...atom }) => {
24
19
  // ensure observers get called in series
25
20
  const enqueue = makeConcurrent((fn) => fn(), { concurrency: 1 })
26
21
 
@@ -30,18 +25,28 @@ const enforceObservableRules = ({ defaultValue, logger, ...atom }) => {
30
25
  const get = () => atom.get().then(postProcessValue)
31
26
 
32
27
  const observe = (listener) => {
33
- const deduped = withChangeDetection(listener)
34
- const publishSerially = (value) => enqueue(() => deduped.listener(value))
28
+ let called = false
29
+ listener = withChangeDetection(listener)
30
+
31
+ const publishSerially = (value) => {
32
+ called = true
33
+ return enqueue(() => listener(value))
34
+ }
35
+
35
36
  // note: call observe() first to give it a chance to throw if it's not supported
36
- const unsubscribe = atom.observe((value) => publishSerially(postProcessValue(value)))
37
37
  // if the subscription already fired once, ignore first get
38
- get().then((value) => !deduped.called && publishSerially(value))
39
- return unsubscribe
38
+ get().then((value) => !called && publishSerially(value))
39
+ return atom.observe((value) => publishSerially(postProcessValue(value)))
40
40
  }
41
41
 
42
42
  const set = async (value) => {
43
43
  // support a function a la React's setState(oldState => newState)
44
- if (typeof value === 'function') value = await value(await get())
44
+ if (typeof value === 'function') {
45
+ const current = getInitialized() ? await get() : defaultValue
46
+ value = await value(current)
47
+
48
+ if (current === value) return
49
+ }
45
50
 
46
51
  await atom.set(value)
47
52
  }
@@ -1,17 +1,9 @@
1
- import enforceObservableRules from '../enforce-rules'
2
-
3
1
  export default function blockUntil({ atom, unblock }) {
4
2
  const get = async () => unblock().then(atom.get)
5
3
 
6
- const { observe, ...rest } = enforceObservableRules({
4
+ return {
7
5
  ...atom,
8
6
  get,
9
- set: atom.set,
10
- observe: atom.observe,
11
- })
12
-
13
- return {
14
- ...rest,
15
7
  observe: (callback) => {
16
8
  let isSubscribed = true
17
9
  let unsubscribe = () => {}
@@ -1,6 +1,6 @@
1
1
  import createCountdownLock from '../countdown-lock'
2
- import enforceObservableRules from '../enforce-rules'
3
2
  import createSimpleObserver from '../simple-observer'
3
+ import enforceObservableRules from '../enforce-rules'
4
4
 
5
5
  const combine = (atoms) => {
6
6
  const { notify, observe } = createSimpleObserver()
@@ -1,5 +1,3 @@
1
- import enforceObservableRules from '../enforce-rules'
2
-
3
1
  const compute = ({ atom, selector }) => {
4
2
  const get = async () => {
5
3
  const values = await atom.get()
@@ -14,7 +12,12 @@ const compute = ({ atom, selector }) => {
14
12
  return atom.observe((values) => callback(selector(values), values))
15
13
  }
16
14
 
17
- return enforceObservableRules({ ...atom, get, set, observe })
15
+ return {
16
+ ...atom,
17
+ get,
18
+ set,
19
+ observe,
20
+ }
18
21
  }
19
22
 
20
23
  export default compute
@@ -0,0 +1,12 @@
1
+ import { isEqual } from 'lodash'
2
+
3
+ const dedupe = (atom) => {
4
+ const set = (value) => atom.set((previous) => (isEqual(previous, value) ? previous : value))
5
+
6
+ return {
7
+ ...atom,
8
+ set,
9
+ }
10
+ }
11
+
12
+ export default dedupe
@@ -1,15 +1,20 @@
1
- import compute from './compute'
2
-
3
1
  const difference = (atom) => {
4
- let prev
2
+ const observe = (callback) => {
3
+ let prev
4
+
5
+ const selector = (value) => {
6
+ const p = prev
7
+ prev = value
8
+ return { previous: p, current: value }
9
+ }
5
10
 
6
- const selector = (value) => {
7
- const p = prev
8
- prev = value
9
- return { previous: p, current: value }
11
+ return atom.observe((value) => callback(selector(value)))
10
12
  }
11
13
 
12
- return compute({ atom, selector })
14
+ return {
15
+ ...atom,
16
+ observe,
17
+ }
13
18
  }
14
19
 
15
20
  export default difference
@@ -1,11 +1,12 @@
1
- import enforceObservableRules from '../enforce-rules'
2
-
3
1
  const readOnly = (atom) => {
4
2
  const set = async () => {
5
3
  throw new Error('selected atom does not support set')
6
4
  }
7
5
 
8
- return enforceObservableRules({ ...atom, set })
6
+ return {
7
+ ...atom,
8
+ set,
9
+ }
9
10
  }
10
11
 
11
12
  export default readOnly
@@ -1,5 +1,4 @@
1
1
  import assert from 'minimalistic-assert'
2
- import enforceObservableRules from '../enforce-rules'
3
2
 
4
3
  const swallowObserverErrors = ({ atom, logger }) => {
5
4
  assert(atom, 'expected "atom')
@@ -14,7 +13,10 @@ const swallowObserverErrors = ({ atom, logger }) => {
14
13
  }
15
14
  })
16
15
 
17
- return enforceObservableRules({ ...atom, observe })
16
+ return {
17
+ ...atom,
18
+ observe,
19
+ }
18
20
  }
19
21
 
20
22
  export default swallowObserverErrors
@@ -1,6 +1,5 @@
1
1
  import delay from 'delay'
2
2
  import assert from 'minimalistic-assert'
3
- import enforceObservableRules from '../enforce-rules'
4
3
 
5
4
  const timeoutObservers = ({ atom, timeout }) => {
6
5
  assert(atom, 'expected "atom')
@@ -19,7 +18,10 @@ const timeoutObservers = ({ atom, timeout }) => {
19
18
  ]).then(() => timeoutPromise.clear())
20
19
  })
21
20
 
22
- return enforceObservableRules({ ...atom, observe })
21
+ return {
22
+ ...atom,
23
+ observe,
24
+ }
23
25
  }
24
26
 
25
27
  export default timeoutObservers
@@ -5,6 +5,10 @@ const withSerialization = ({ atom, serialize, deserialize }) => {
5
5
  }
6
6
 
7
7
  const set = async (value) => {
8
+ if (typeof value === 'function') {
9
+ return atom.set((previousValue) => serialize(value(deserialize(previousValue))))
10
+ }
11
+
8
12
  const serialized = serialize(value)
9
13
  return atom.set(serialized)
10
14
  }
@@ -1,12 +1,13 @@
1
1
  import enforceObservableRules from './enforce-rules'
2
2
 
3
- const fromEventEmitter = ({ emitter, event, get, set, defaultValue, logger }) => {
3
+ const fromEventEmitter = ({ emitter, event, get, set, defaultValue, logger, getInitialized }) => {
4
4
  const observe = (listener) => {
5
5
  emitter.on(event, listener)
6
6
  return () => emitter.removeListener(event, listener)
7
7
  }
8
8
 
9
9
  return enforceObservableRules({
10
+ getInitialized,
10
11
  get,
11
12
  set,
12
13
  observe,
@@ -30,7 +30,14 @@ const createAtomMock = (options = {}) => {
30
30
  }
31
31
  }
32
32
 
33
- return fromEventEmitter({ ...options, emitter, event: 'data', get, set })
33
+ return fromEventEmitter({
34
+ ...options,
35
+ getInitialized: () => initialized.resolved,
36
+ emitter,
37
+ event: 'data',
38
+ get,
39
+ set,
40
+ })
34
41
  }
35
42
 
36
43
  export default createAtomMock
@@ -1,4 +1,4 @@
1
- import { get as getValueAtPath } from 'lodash'
1
+ import { get as getValueAtPath, isEqual } from 'lodash'
2
2
 
3
3
  import createSimpleObserver from '../simple-observer'
4
4
  import enforceObservableRules from '../enforce-rules'
@@ -30,10 +30,19 @@ const createRemoteConfigAtomFactory =
30
30
  return notify(data)
31
31
  })
32
32
 
33
+ const observeDistinct = (callback) =>
34
+ observe(
35
+ function (value) {
36
+ if (isEqual(this.lastValue, value)) return
37
+ this.lastValue = value
38
+ callback(value)
39
+ }.bind({})
40
+ )
41
+
33
42
  return enforceObservableRules({
34
43
  get,
35
44
  set,
36
- observe,
45
+ observe: observeDistinct,
37
46
  defaultValue,
38
47
  })
39
48
  }
package/src/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  export { default as createAtomMock } from './factories/memory'
2
2
  export { default as createInMemoryAtom } from './factories/memory'
3
- export { default as createFusionAtomFactory } from './factories/fusion'
4
3
  export { default as createStorageAtomFactory } from './factories/storage'
5
- export { default as createLocalConfigAtomFactory } from './factories/local-config'
6
4
  export { default as createRemoteConfigAtomFactory } from './factories/remote-config'
7
5
  export { default as createKeystoreAtom } from './factories/keystore'
8
6
  export { default as createSequencedKeystoreAtom } from './factories/sequenced-keystore'
@@ -13,6 +11,7 @@ export { default as difference } from './enhancers/difference'
13
11
  export { default as withSerialization } from './enhancers/with-serialization'
14
12
  export { default as combine } from './enhancers/combine'
15
13
  export { default as readOnly } from './enhancers/read-only'
14
+ export { default as dedupe } from './enhancers/dedupe'
16
15
  export { default as warnOnSameValueSet } from './enhancers/warn-on-same-value-set'
17
16
  export { default as swallowObserverErrors } from './enhancers/swallow-observer-errors'
18
17
  export { default as timeoutObservers } from './enhancers/timeout-observers'
@@ -1,30 +0,0 @@
1
- import { get as getValueAtPath, set as setValueAtPath } from 'lodash'
2
-
3
- import enforceObservableRules from '../enforce-rules'
4
-
5
- const createFusionAtomFactory =
6
- ({ fusion, logger }) =>
7
- ({ path, defaultValue }) => {
8
- const set = async (value) => {
9
- await fusion.mergeProfile(setValueAtPath({}, path, value))
10
- }
11
-
12
- const get = async () => {
13
- const profile = await fusion.getProfile()
14
- return getValueFromProfile(profile)
15
- }
16
-
17
- const getValueFromProfile = (profile) => getValueAtPath(profile, path)
18
-
19
- const observe = (listener) => fusion.subscribe(getValueFromProfile, listener)
20
-
21
- return enforceObservableRules({
22
- get,
23
- set,
24
- observe,
25
- defaultValue,
26
- logger,
27
- })
28
- }
29
-
30
- export default createFusionAtomFactory
@@ -1,19 +0,0 @@
1
- import createSimpleObserver from '../simple-observer'
2
- import enforceObservableRules from '../enforce-rules'
3
-
4
- const createLocalConfigAtomFactory =
5
- ({ localConfig }) =>
6
- ({ key, defaultValue }) => {
7
- const { notify, observe } = createSimpleObserver()
8
-
9
- localConfig.on(`update:${key}`, ({ current }) => notify(current))
10
-
11
- return enforceObservableRules({
12
- get: () => localConfig.get(key),
13
- set: (value) => localConfig.set(key, value),
14
- observe,
15
- defaultValue,
16
- })
17
- }
18
-
19
- export default createLocalConfigAtomFactory