@exodus/atoms 5.7.3 → 6.0.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/countdown-lock.d.ts +5 -0
  3. package/lib/countdown-lock.js +24 -0
  4. package/lib/effects/wait-until.d.ts +11 -0
  5. package/lib/effects/wait-until.js +28 -0
  6. package/lib/enforce-rules.d.ts +8 -0
  7. package/lib/enforce-rules.js +56 -0
  8. package/lib/enhancers/block-until.d.ts +12 -0
  9. package/lib/enhancers/block-until.js +20 -0
  10. package/lib/enhancers/combine.d.ts +8 -0
  11. package/lib/enhancers/combine.js +47 -0
  12. package/lib/enhancers/compute.d.ts +7 -0
  13. package/lib/enhancers/compute.js +28 -0
  14. package/lib/enhancers/dedupe.d.ts +3 -0
  15. package/lib/enhancers/dedupe.js +29 -0
  16. package/lib/enhancers/difference.d.ts +9 -0
  17. package/lib/enhancers/difference.js +16 -0
  18. package/lib/enhancers/optimistic-notifier.d.ts +3 -0
  19. package/lib/enhancers/optimistic-notifier.js +48 -0
  20. package/lib/enhancers/read-only.d.ts +3 -0
  21. package/lib/enhancers/read-only.js +10 -0
  22. package/lib/enhancers/swallow-observer-errors.d.ts +12 -0
  23. package/lib/enhancers/swallow-observer-errors.js +18 -0
  24. package/lib/enhancers/timeout-observers.d.ts +7 -0
  25. package/lib/enhancers/timeout-observers.js +20 -0
  26. package/lib/enhancers/warn-on-same-value-set.d.ts +8 -0
  27. package/lib/enhancers/warn-on-same-value-set.js +30 -0
  28. package/lib/enhancers/with-serialization.d.ts +8 -0
  29. package/lib/enhancers/with-serialization.js +19 -0
  30. package/lib/event-emitter.d.ts +11 -0
  31. package/lib/event-emitter.js +15 -0
  32. package/lib/factories/keystore.d.ts +14 -0
  33. package/lib/factories/keystore.js +23 -0
  34. package/lib/factories/memory.d.ts +6 -0
  35. package/lib/factories/memory.js +35 -0
  36. package/lib/factories/observer.d.ts +13 -0
  37. package/lib/factories/observer.js +28 -0
  38. package/lib/factories/remote-config.d.ts +13 -0
  39. package/lib/factories/remote-config.js +34 -0
  40. package/lib/factories/sequenced-keystore.d.ts +15 -0
  41. package/lib/factories/sequenced-keystore.js +71 -0
  42. package/lib/factories/storage.d.ts +12 -0
  43. package/lib/factories/storage.js +46 -0
  44. package/lib/index.d.ts +22 -0
  45. package/{src → lib}/index.js +21 -24
  46. package/lib/simple-observer.d.ts +8 -0
  47. package/lib/simple-observer.js +18 -0
  48. package/lib/utils/guards.d.ts +2 -0
  49. package/lib/utils/guards.js +3 -0
  50. package/lib/utils/types.d.ts +24 -0
  51. package/lib/utils/types.js +1 -0
  52. package/package.json +10 -4
  53. package/src/countdown-lock.js +0 -29
  54. package/src/effects/wait-until.js +0 -29
  55. package/src/enforce-rules.js +0 -74
  56. package/src/enhancers/block-until.js +0 -23
  57. package/src/enhancers/combine.js +0 -59
  58. package/src/enhancers/compute.js +0 -31
  59. package/src/enhancers/dedupe.js +0 -31
  60. package/src/enhancers/difference.js +0 -20
  61. package/src/enhancers/optimistic-notifier.js +0 -60
  62. package/src/enhancers/read-only.js +0 -12
  63. package/src/enhancers/swallow-observer-errors.js +0 -22
  64. package/src/enhancers/timeout-observers.js +0 -27
  65. package/src/enhancers/warn-on-same-value-set.js +0 -49
  66. package/src/enhancers/with-serialization.js +0 -23
  67. package/src/event-emitter.js +0 -19
  68. package/src/factories/keystore.js +0 -40
  69. package/src/factories/memory.js +0 -46
  70. package/src/factories/observer.js +0 -33
  71. package/src/factories/remote-config.js +0 -50
  72. package/src/factories/sequenced-keystore.js +0 -108
  73. package/src/factories/storage.js +0 -60
  74. package/src/simple-observer.js +0 -23
@@ -1,59 +0,0 @@
1
- import createCountdownLock from '../countdown-lock'
2
- import createSimpleObserver from '../simple-observer'
3
- import enforceObservableRules from '../enforce-rules'
4
-
5
- const combine = (atoms) => {
6
- const { notify, observe: observeSimpleObserver } = createSimpleObserver()
7
- const countdownLock = createCountdownLock(Object.keys(atoms))
8
-
9
- let values = {}
10
- let subscriptions = []
11
- let subscribers = 0
12
-
13
- const maybeUnsubscribeSourceObservables = () => {
14
- subscribers -= 1
15
- if (subscribers > 0) return
16
- subscriptions.forEach((unsubscribe) => unsubscribe())
17
- subscriptions = []
18
- }
19
-
20
- const observe = (callback) => {
21
- subscribers += 1
22
-
23
- if (subscribers === 1) {
24
- subscriptions = Object.entries(atoms).map(([name, atom]) =>
25
- atom.observe(async (value) => {
26
- values = {
27
- ...values,
28
- [name]: value,
29
- }
30
- if (countdownLock.unlock(name)) await notify(values)
31
- })
32
- )
33
- }
34
-
35
- const unsubscribe = observeSimpleObserver(callback)
36
- return () => {
37
- unsubscribe()
38
- maybeUnsubscribeSourceObservables()
39
- }
40
- }
41
-
42
- const get = async () => {
43
- return Object.fromEntries(
44
- await Promise.all(Object.entries(atoms).map(async ([name, atom]) => [name, await atom.get()]))
45
- )
46
- }
47
-
48
- const set = async () => {
49
- throw new Error('combine does not support method: set')
50
- }
51
-
52
- return enforceObservableRules({
53
- get,
54
- set,
55
- observe,
56
- })
57
- }
58
-
59
- export default combine
@@ -1,31 +0,0 @@
1
- const compute = ({ atom, selector }) => {
2
- const get = async () => {
3
- const values = await atom.get()
4
- return selector(values)
5
- }
6
-
7
- const set = async () => {
8
- throw new Error('selected atom does not support set')
9
- }
10
-
11
- const observe = (callback) => {
12
- let prev
13
- let called
14
- return atom.observe((values) => {
15
- const selected = selector(values)
16
- if (called && prev === selected) return
17
- called = true
18
- prev = selected
19
- return callback(selected)
20
- })
21
- }
22
-
23
- return {
24
- ...atom,
25
- get,
26
- set,
27
- observe,
28
- }
29
- }
30
-
31
- export default compute
@@ -1,31 +0,0 @@
1
- import { isEqual } from 'lodash'
2
-
3
- const dedupe = (atom) => {
4
- const set = (value) => {
5
- // avoid triggering a get() when we're resetting the atom
6
- // TODO: add atom.unset and stop using atom.set(undefined)
7
- if (value === undefined) return atom.set(value)
8
-
9
- return atom.set((previous) => (isEqual(previous, value) ? previous : value))
10
- }
11
-
12
- const observe = (callback) => {
13
- let called
14
- let previous
15
- return atom.observe(async (value) => {
16
- if (called && isEqual(previous, value)) return
17
-
18
- called = true
19
- previous = value
20
- return callback(value)
21
- })
22
- }
23
-
24
- return {
25
- ...atom,
26
- set,
27
- observe,
28
- }
29
- }
30
-
31
- export default dedupe
@@ -1,20 +0,0 @@
1
- const difference = (atom) => {
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
- }
10
-
11
- return atom.observe((value) => callback(selector(value)))
12
- }
13
-
14
- return {
15
- ...atom,
16
- observe,
17
- }
18
- }
19
-
20
- export default difference
@@ -1,60 +0,0 @@
1
- import makeConcurrent from 'make-concurrent'
2
- import createInMemoryAtom from '../factories/memory'
3
-
4
- const optimisticNotifier = (atom) => {
5
- const memoryAtom = createInMemoryAtom()
6
-
7
- const set = makeConcurrent(async (newValue) => {
8
- let previous
9
- await memoryAtom.set((previousValue) => {
10
- previous = previousValue
11
- return newValue
12
- })
13
-
14
- atom
15
- .set(newValue)
16
- // only revert in case the value hasn't changed in the meanwhile
17
- .catch(() => memoryAtom.set((value) => (value === newValue ? previous : value)))
18
- })
19
-
20
- let unsubscribeSource
21
- let subscribers = 0
22
-
23
- const maybeUnsubscribeSource = () => {
24
- subscribers -= 1
25
- if (subscribers > 0) return
26
- unsubscribeSource()
27
- unsubscribeSource = undefined
28
- }
29
-
30
- const observe = (callback) => {
31
- if (subscribers === 0) {
32
- unsubscribeSource = atom.observe(set)
33
- }
34
-
35
- subscribers += 1
36
- const unsubscribe = memoryAtom.observe(callback)
37
-
38
- return () => {
39
- unsubscribe()
40
- maybeUnsubscribeSource()
41
- }
42
- }
43
-
44
- const get = async () => {
45
- if (subscribers === 0) {
46
- return atom.get()
47
- }
48
-
49
- return memoryAtom.get()
50
- }
51
-
52
- return {
53
- ...memoryAtom,
54
- set,
55
- get,
56
- observe,
57
- }
58
- }
59
-
60
- export default optimisticNotifier
@@ -1,12 +0,0 @@
1
- const readOnly = (atom) => {
2
- const set = async () => {
3
- throw new Error('selected atom does not support set')
4
- }
5
-
6
- return {
7
- ...atom,
8
- set,
9
- }
10
- }
11
-
12
- export default readOnly
@@ -1,22 +0,0 @@
1
- import assert from 'minimalistic-assert'
2
-
3
- const swallowObserverErrors = ({ atom, logger }) => {
4
- assert(atom, 'expected "atom')
5
- assert(typeof logger?.error === 'function', 'expected logger with ".error" function')
6
-
7
- const observe = (observer) =>
8
- atom.observe(async (...args) => {
9
- try {
10
- await observer(...args)
11
- } catch (err) {
12
- logger.error('Observer threw error', err)
13
- }
14
- })
15
-
16
- return {
17
- ...atom,
18
- observe,
19
- }
20
- }
21
-
22
- export default swallowObserverErrors
@@ -1,27 +0,0 @@
1
- import delay from 'delay'
2
- import assert from 'minimalistic-assert'
3
-
4
- const timeoutObservers = ({ atom, timeout }) => {
5
- assert(atom, 'expected "atom')
6
- assert(typeof timeout === 'number', 'expected number "timeout"')
7
-
8
- const observe = (observer) =>
9
- atom.observe(async (...args) => {
10
- const timeoutPromise = delay.reject(timeout, {
11
- value: new Error('Observer timed out!'),
12
- })
13
-
14
- await Promise.race([
15
- //
16
- observer(...args),
17
- timeoutPromise,
18
- ]).then(() => timeoutPromise.clear())
19
- })
20
-
21
- return {
22
- ...atom,
23
- observe,
24
- }
25
- }
26
-
27
- export default timeoutObservers
@@ -1,49 +0,0 @@
1
- import assert from 'minimalistic-assert'
2
- import { isEqual as deepEqual } from 'lodash'
3
-
4
- const STRINGIFY_LIMIT = 1000
5
-
6
- /**
7
- * Serialize an object to a JSON string with a limit on the number of properties shown
8
- * @param {Object} obj - The object to be serialized
9
- * @param {number} limit - The maximum number of properties to include in the output
10
- * @returns {string} - The JSON string representation of the object
11
- */
12
- const limitedStringify = (obj, limit = STRINGIFY_LIMIT) => {
13
- let count = 0
14
-
15
- return JSON.stringify(obj, (_, value) => {
16
- if (count > limit) {
17
- return
18
- }
19
-
20
- if (count === limit) {
21
- count++
22
- return '...more properties not shown'
23
- }
24
-
25
- count++
26
- return value
27
- })
28
- }
29
-
30
- const warnOnSameValueSet = ({ atom, isEqual = deepEqual, logger }) => {
31
- assert(atom, 'expected "atom')
32
- assert(typeof logger?.warn === 'function', 'expected logger with ".warn" function')
33
-
34
- let previousValue
35
- atom.observe((value) => {
36
- if (value !== undefined && isEqual(value, previousValue)) {
37
- // limit value shown to avoid a CPU spike that can lead to Android freezing
38
- logger.warn(
39
- `Atom was called with the same value it currently holds: ${limitedStringify(value)}`
40
- )
41
- }
42
-
43
- previousValue = value
44
- })
45
-
46
- return atom
47
- }
48
-
49
- export default warnOnSameValueSet
@@ -1,23 +0,0 @@
1
- const withSerialization = ({ atom, serialize, deserialize }) => {
2
- const get = async () => {
3
- const serialized = await atom.get()
4
- return deserialize(serialized)
5
- }
6
-
7
- const set = async (value) => {
8
- if (typeof value === 'function') {
9
- return atom.set((previousValue) => serialize(value(deserialize(previousValue))))
10
- }
11
-
12
- const serialized = serialize(value)
13
- return atom.set(serialized)
14
- }
15
-
16
- const observe = (callback) => {
17
- return atom.observe((value) => callback(deserialize(value), value))
18
- }
19
-
20
- return { ...atom, get, set, observe }
21
- }
22
-
23
- export default withSerialization
@@ -1,19 +0,0 @@
1
- import enforceObservableRules from './enforce-rules'
2
-
3
- const fromEventEmitter = ({ emitter, event, get, set, defaultValue, logger, getInitialized }) => {
4
- const observe = (listener) => {
5
- emitter.on(event, listener)
6
- return () => emitter.removeListener(event, listener)
7
- }
8
-
9
- return enforceObservableRules({
10
- getInitialized,
11
- get,
12
- set,
13
- observe,
14
- defaultValue,
15
- logger,
16
- })
17
- }
18
-
19
- export default fromEventEmitter
@@ -1,40 +0,0 @@
1
- import enforceObservableRules from '../enforce-rules'
2
- import createSimpleObserver from '../simple-observer'
3
-
4
- const createKeystoreAtom = ({
5
- keystore,
6
- logger,
7
- config: {
8
- //
9
- key,
10
- defaultValue,
11
- isSoleWriter,
12
- getOpts,
13
- setOpts,
14
- deleteOpts,
15
- },
16
- }) => {
17
- const { notify, observe } = createSimpleObserver({ enable: isSoleWriter })
18
-
19
- const set = async (value) => {
20
- if (value == null) {
21
- await keystore.deleteSecret(key, deleteOpts)
22
- } else {
23
- await keystore.setSecret(key, value, setOpts)
24
- }
25
-
26
- if (isSoleWriter) await notify(value)
27
- }
28
-
29
- const get = () => keystore.getSecret(key, getOpts)
30
-
31
- return enforceObservableRules({
32
- get,
33
- set,
34
- observe,
35
- defaultValue,
36
- logger,
37
- })
38
- }
39
-
40
- export default createKeystoreAtom
@@ -1,46 +0,0 @@
1
- import EventEmitter from 'events/'
2
-
3
- import fromEventEmitter from '../event-emitter'
4
- import pDefer from 'p-defer'
5
-
6
- const createAtomMock = (options = {}) => {
7
- const { defaultValue } = options
8
-
9
- let latestValue = defaultValue
10
-
11
- const emitter = new EventEmitter()
12
- emitter.setMaxListeners(Number.POSITIVE_INFINITY)
13
-
14
- const initialized = pDefer()
15
-
16
- const get = async () => {
17
- if (!('defaultValue' in options)) {
18
- await initialized.promise
19
- }
20
-
21
- return latestValue
22
- }
23
-
24
- const set = (data) => {
25
- latestValue = data
26
- if (data !== undefined) {
27
- initialized.resolve()
28
- initialized.resolved = true
29
- }
30
-
31
- if (initialized.resolved) {
32
- emitter.emit('data', data)
33
- }
34
- }
35
-
36
- return fromEventEmitter({
37
- ...options,
38
- getInitialized: () => initialized.resolved,
39
- emitter,
40
- event: 'data',
41
- get,
42
- set,
43
- })
44
- }
45
-
46
- export default createAtomMock
@@ -1,33 +0,0 @@
1
- const createAtomObserver = ({ port, atom, event, immediateRegister = true }) => {
2
- let emitting = false
3
- let unobserve
4
-
5
- const register = () => {
6
- if (unobserve) return
7
-
8
- unobserve = atom.observe((value) => {
9
- if (emitting) port.emit(event, value)
10
- })
11
- }
12
-
13
- const unregister = () => {
14
- unobserve?.()
15
- }
16
-
17
- const start = async () => {
18
- emitting = true
19
- port.emit(event, await atom.get())
20
- }
21
-
22
- if (immediateRegister) {
23
- register()
24
- }
25
-
26
- return {
27
- register,
28
- unregister,
29
- start,
30
- }
31
- }
32
-
33
- export default createAtomObserver
@@ -1,50 +0,0 @@
1
- import { get as getValueAtPath, isEqual } from 'lodash'
2
-
3
- import createSimpleObserver from '../simple-observer'
4
- import enforceObservableRules from '../enforce-rules'
5
-
6
- const createRemoteConfigAtomFactory =
7
- ({ remoteConfig }) =>
8
- ({ path, selector, defaultValue }) => {
9
- if (path && selector) {
10
- throw new Error(
11
- 'Provide either a path or a selector to get data from remote config - not both.'
12
- )
13
- }
14
-
15
- const getValue = (value) => (selector ? selector(value) : getValueAtPath(value, path))
16
-
17
- const { notify, observe } = createSimpleObserver()
18
-
19
- const get = async () => {
20
- const data = await remoteConfig.getAll()
21
- return getValue(data)
22
- }
23
-
24
- const set = async () => {
25
- throw new Error('remoteConfig is read-only')
26
- }
27
-
28
- remoteConfig.on('sync', async ({ current }) => {
29
- const data = getValue(current)
30
- return notify(data)
31
- })
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
-
42
- return enforceObservableRules({
43
- get,
44
- set,
45
- observe: observeDistinct,
46
- defaultValue,
47
- })
48
- }
49
-
50
- export default createRemoteConfigAtomFactory
@@ -1,108 +0,0 @@
1
- import assert from 'minimalistic-assert'
2
-
3
- import enforceObservableRules from '../enforce-rules'
4
- import createSimpleObserver from '../simple-observer'
5
-
6
- const getRange = (offset, length) => Array.from({ length }, (_, i) => i + offset)
7
-
8
- const createSequencedKeystoreAtom = ({
9
- keystore,
10
- config: {
11
- //
12
- key,
13
- separator = '.',
14
- defaultValue = [],
15
- isSoleWriter,
16
- getOpts,
17
- setOpts,
18
- deleteOpts,
19
- },
20
- }) => {
21
- assert(key, 'sequence keystore atom: key missing')
22
-
23
- assert(typeof key === 'string', 'sequence keystore atom: key must be a string')
24
-
25
- assert(Array.isArray(defaultValue), 'sequence keystore atom: default value must be an array')
26
-
27
- const { notify, observe } = createSimpleObserver({ enable: isSoleWriter })
28
-
29
- let cache
30
-
31
- const getKey = (index) =>
32
- key.endsWith(separator) ? `${key}${index}` : `${key}${separator}${index}`
33
-
34
- const getByIndex = async (index) => {
35
- const key = getKey(index)
36
- const value = await keystore.getSecret(key, getOpts)
37
- if (value) return { key, value }
38
- }
39
-
40
- const list = async () => {
41
- let result = []
42
- let batch = []
43
- let offset = 0
44
- const batchSize = 5
45
-
46
- // paginate until we're out of items
47
- do {
48
- const range = getRange(offset, batchSize)
49
- batch = await Promise.all(range.map(getByIndex))
50
- batch = batch.filter(Boolean)
51
- result = [...result, ...batch]
52
- offset += batchSize
53
- } while (batch.length > 0)
54
-
55
- return result
56
- }
57
-
58
- const clear = async () => {
59
- const items = await list()
60
- await Promise.all(items.map((item) => keystore.deleteSecret(item.key, deleteOpts)))
61
- }
62
-
63
- const _set = async (value) => {
64
- assert(Array.isArray(value), 'sequence keystore atom: set value must be an array')
65
-
66
- await Promise.all(
67
- value.map((value, index) => {
68
- return keystore.setSecret(getKey(index), value, setOpts)
69
- })
70
- )
71
- }
72
-
73
- const set = async (value) => {
74
- await clear()
75
-
76
- if (value !== undefined) {
77
- await _set(value)
78
- }
79
-
80
- if (isSoleWriter) {
81
- cache = value
82
- await notify(value)
83
- }
84
- }
85
-
86
- const get = async () => {
87
- if (cache) return cache
88
-
89
- const items = await list()
90
- const value = items.map((item) => item.value)
91
-
92
- if (isSoleWriter) {
93
- cache = value
94
- }
95
-
96
- // let default value be used
97
- return items.length === 0 ? undefined : value
98
- }
99
-
100
- return enforceObservableRules({
101
- get,
102
- set,
103
- observe,
104
- defaultValue,
105
- })
106
- }
107
-
108
- export default createSequencedKeystoreAtom
@@ -1,60 +0,0 @@
1
- import createSimpleObserver from '../simple-observer'
2
- import enforceObservableRules from '../enforce-rules'
3
- import pDefer from 'p-defer'
4
-
5
- const createStorageAtomFactory =
6
- ({ storage, logger }) =>
7
- ({ key, defaultValue, isSoleWriter }) => {
8
- const { notify, observe } = createSimpleObserver({ enable: isSoleWriter })
9
-
10
- let cached
11
- let writePromiseDefer
12
- const set = async (value) => {
13
- if (value === undefined) {
14
- writePromiseDefer = pDefer()
15
- await storage.delete(key)
16
- } else {
17
- writePromiseDefer = pDefer()
18
- await storage.set(key, value)
19
- }
20
-
21
- writePromiseDefer.resolve()
22
-
23
- if (isSoleWriter) {
24
- cached = value
25
- await notify(value)
26
- }
27
- }
28
-
29
- const get = async () => {
30
- if (cached) {
31
- return cached
32
- }
33
-
34
- if (writePromiseDefer) {
35
- await writePromiseDefer.promise
36
- }
37
-
38
- if (cached) {
39
- return cached
40
- }
41
-
42
- const value = await storage.get(key)
43
-
44
- if (isSoleWriter) {
45
- cached = value
46
- }
47
-
48
- return value
49
- }
50
-
51
- return enforceObservableRules({
52
- get,
53
- set,
54
- observe,
55
- defaultValue,
56
- logger,
57
- })
58
- }
59
-
60
- export default createStorageAtomFactory