atom.io 0.33.20 → 0.34.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 (31) hide show
  1. package/dist/internal/index.d.ts +5 -6
  2. package/dist/internal/index.d.ts.map +1 -1
  3. package/dist/internal/index.js +1257 -1249
  4. package/dist/internal/index.js.map +1 -1
  5. package/dist/react/index.js +22 -11
  6. package/dist/react/index.js.map +1 -1
  7. package/dist/realtime-client/index.js +2 -2
  8. package/dist/realtime-client/index.js.map +1 -1
  9. package/dist/realtime-testing/index.d.ts.map +1 -1
  10. package/dist/realtime-testing/index.js +0 -1
  11. package/dist/realtime-testing/index.js.map +1 -1
  12. package/package.json +2 -2
  13. package/src/internal/atom/create-regular-atom.ts +6 -7
  14. package/src/internal/caching.ts +10 -9
  15. package/src/internal/future.ts +3 -0
  16. package/src/internal/get-state/read-or-compute-value.ts +12 -7
  17. package/src/internal/mutable/create-mutable-atom.ts +2 -4
  18. package/src/internal/selector/create-readonly-held-selector.ts +10 -0
  19. package/src/internal/selector/create-readonly-pure-selector.ts +13 -4
  20. package/src/internal/selector/create-writable-held-selector.ts +11 -2
  21. package/src/internal/selector/create-writable-pure-selector.ts +11 -1
  22. package/src/internal/selector/trace-selector-atoms.ts +18 -40
  23. package/src/internal/selector/update-selector-atoms.ts +5 -5
  24. package/src/internal/set-state/evict-downstream.ts +2 -2
  25. package/src/internal/set-state/reset-atom-or-selector.ts +3 -3
  26. package/src/internal/store/store.ts +0 -1
  27. package/src/internal/subscribe/subscribe-to-root-atoms.ts +42 -38
  28. package/src/internal/subscribe/subscribe-to-state.ts +23 -11
  29. package/src/react/use-loadable.ts +18 -13
  30. package/src/realtime-client/realtime-client-stores/client-sync-store.ts +2 -2
  31. package/src/realtime-testing/setup-realtime-test.tsx +0 -5
@@ -29,8 +29,18 @@ export const createWritableHeldSelector = <T extends object>(
29
29
  const getterToolkit = { find, get, json }
30
30
 
31
31
  const getSelf = (getFn = options.get, innerTarget = newest(store)): T => {
32
+ const upstreamStates = innerTarget.selectorGraph.getRelationEntries({
33
+ downstreamSelectorKey: key,
34
+ })
35
+ for (const [downstreamSelectorKey, { source }] of upstreamStates) {
36
+ if (source !== key) {
37
+ innerTarget.selectorGraph.delete(downstreamSelectorKey, key)
38
+ }
39
+ }
40
+ innerTarget.selectorAtoms.delete(key)
32
41
  getFn(getterToolkit, constant)
33
42
  cacheValue(innerTarget, key, constant, subject)
43
+ store.logger.info(`✨`, type, key, `=`, constant)
34
44
  covered.clear()
35
45
  return constant
36
46
  }
@@ -57,8 +67,7 @@ export const createWritableHeldSelector = <T extends object>(
57
67
  ...(family && { family }),
58
68
  }
59
69
  target.writableSelectors.set(key, mySelector)
60
- const initialValue = getSelf()
61
- store.logger.info(`✨`, type, key, `=`, initialValue)
70
+ // const initialValue = getSelf()
62
71
 
63
72
  const token: WritableHeldSelectorToken<T> = { key, type }
64
73
  if (family) {
@@ -29,8 +29,18 @@ export const createWritablePureSelector = <T>(
29
29
  const getterToolkit = { find, get, json }
30
30
 
31
31
  const getSelf = (getFn = options.get, innerTarget = newest(store)): T => {
32
+ const upstreamStates = innerTarget.selectorGraph.getRelationEntries({
33
+ downstreamSelectorKey: key,
34
+ })
35
+ for (const [downstreamSelectorKey, { source }] of upstreamStates) {
36
+ if (source !== key) {
37
+ innerTarget.selectorGraph.delete(downstreamSelectorKey, key)
38
+ }
39
+ }
40
+ innerTarget.selectorAtoms.delete(key)
32
41
  const value = getFn(getterToolkit)
33
- cacheValue(innerTarget, key, value, subject)
42
+ const cached = cacheValue(innerTarget, key, value, subject)
43
+ store.logger.info(`✨`, type, key, `=`, cached)
34
44
  covered.clear()
35
45
  return value
36
46
  }
@@ -1,51 +1,29 @@
1
- import type { Atom, Selector, Store } from ".."
2
- import type { AtomKey, StateKey } from "../keys"
1
+ import type { Atom, Store } from ".."
3
2
  import { isAtomKey } from "../keys"
4
3
  import { getSelectorDependencyKeys } from "./get-selector-dependency-keys"
5
4
 
6
- export const traceSelectorAtoms = (
5
+ export const traceRootSelectorAtoms = (
7
6
  store: Store,
8
- directDependencyKey: StateKey<unknown>,
9
- covered: Set<string>,
10
- ): AtomKey<unknown>[] => {
11
- const rootKeys: AtomKey<unknown>[] = []
7
+ selectorKey: string,
8
+ covered: Set<string> = new Set<string>(),
9
+ ): Map<string, Atom<unknown>> => {
10
+ const dependencies = getSelectorDependencyKeys(store, selectorKey)
12
11
 
13
- const indirectDependencyKeys = getSelectorDependencyKeys(
14
- store,
15
- directDependencyKey,
16
- )
17
- while (indirectDependencyKeys.length > 0) {
12
+ const roots = new Map<string, Atom<unknown>>()
13
+
14
+ while (dependencies.length > 0) {
18
15
  // biome-ignore lint/style/noNonNullAssertion: just checked length ^^^
19
- const indirectDependencyKey = indirectDependencyKeys.shift()!
20
- if (covered.has(indirectDependencyKey)) {
16
+ const dependencyKey = dependencies.pop()!
17
+ if (covered.has(dependencyKey)) {
21
18
  continue
22
19
  }
23
- covered.add(indirectDependencyKey)
24
-
25
- if (!isAtomKey(store, indirectDependencyKey)) {
26
- indirectDependencyKeys.push(
27
- ...getSelectorDependencyKeys(store, indirectDependencyKey),
28
- )
29
- } else if (!rootKeys.includes(indirectDependencyKey)) {
30
- rootKeys.push(indirectDependencyKey)
20
+ covered.add(dependencyKey)
21
+ if (isAtomKey(store, dependencyKey)) {
22
+ const atom = store.atoms.get(dependencyKey) as Atom<unknown>
23
+ roots.set(atom.key, atom)
24
+ } else {
25
+ dependencies.push(...getSelectorDependencyKeys(store, dependencyKey))
31
26
  }
32
27
  }
33
-
34
- return rootKeys
35
- }
36
-
37
- export const traceAllSelectorAtoms = (
38
- selector: Selector<any>,
39
- store: Store,
40
- ): Atom<unknown>[] => {
41
- const selectorKey = selector.key
42
- const directDependencyKeys = getSelectorDependencyKeys(store, selectorKey)
43
- const covered = new Set<string>()
44
- return directDependencyKeys
45
- .flatMap((depKey) =>
46
- isAtomKey(store, depKey)
47
- ? depKey
48
- : traceSelectorAtoms(store, depKey, covered),
49
- )
50
- .map((atomKey) => store.atoms.get(atomKey) as Atom<unknown>)
28
+ return roots
51
29
  }
@@ -2,7 +2,7 @@ import type { ReadonlyPureSelectorToken, WritableToken } from "atom.io"
2
2
 
3
3
  import { newest } from "../lineage"
4
4
  import type { Store } from "../store"
5
- import { traceSelectorAtoms } from "./trace-selector-atoms"
5
+ import { traceRootSelectorAtoms } from "./trace-selector-atoms"
6
6
 
7
7
  export const updateSelectorAtoms = (
8
8
  store: Store,
@@ -29,16 +29,16 @@ export const updateSelectorAtoms = (
29
29
  `discovers root atom "${dependencyKey}"`,
30
30
  )
31
31
  } else {
32
- const rootKeys = traceSelectorAtoms(store, dependencyKey, covered)
32
+ const rootKeys = traceRootSelectorAtoms(store, dependencyKey, covered)
33
33
  store.logger.info(
34
34
  `🔍`,
35
35
  selectorType,
36
36
  selectorKey,
37
- `discovers root atoms: [ ${rootKeys
38
- .map((key) => `"${key}"`)
37
+ `discovers root atoms: [ ${[...rootKeys.values()]
38
+ .map((root) => `"${root.key}"`)
39
39
  .join(`, `)} ]`,
40
40
  )
41
- for (const atomKey of rootKeys) {
41
+ for (const { key: atomKey } of rootKeys.values()) {
42
42
  target.selectorAtoms = target.selectorAtoms.set({
43
43
  selectorKey,
44
44
  atomKey,
@@ -29,7 +29,7 @@ export function evictDownStream(store: Store, atom: Atom<any>): void {
29
29
  if (isDone(target, key)) {
30
30
  continue
31
31
  }
32
- evictCachedValue(key, target)
32
+ evictCachedValue(target, key)
33
33
  markDone(target, key)
34
34
  }
35
35
  }
@@ -49,7 +49,7 @@ export function evictDownStreamFromSelector(
49
49
  if (isDone(target, downstreamSelectorKey)) {
50
50
  continue
51
51
  }
52
- evictCachedValue(downstreamSelectorKey, target)
52
+ evictCachedValue(target, downstreamSelectorKey)
53
53
  markDone(target, downstreamSelectorKey)
54
54
  }
55
55
  }
@@ -1,5 +1,5 @@
1
1
  import type { Atom, WritableState } from ".."
2
- import { traceAllSelectorAtoms } from ".."
2
+ import { traceRootSelectorAtoms } from ".."
3
3
  import type { Store } from "../store"
4
4
  import { setAtom } from "./set-atom"
5
5
 
@@ -23,8 +23,8 @@ export function resetAtomOrSelector(
23
23
  case `writable_pure_selector`:
24
24
  case `writable_held_selector`:
25
25
  {
26
- const atoms = traceAllSelectorAtoms(state, store)
27
- for (const atom of atoms) {
26
+ const atoms = traceRootSelectorAtoms(store, state.key)
27
+ for (const atom of atoms.values()) {
28
28
  resetAtom(store, atom)
29
29
  }
30
30
  }
@@ -193,7 +193,6 @@ export class Store implements Lineage {
193
193
  ...config,
194
194
  }
195
195
  if (store !== null) {
196
- this.valueMap = new Map(store?.valueMap)
197
196
  this.operation = { ...store?.operation }
198
197
  if (isRootStore(store)) {
199
198
  this.transactionMeta = {
@@ -1,46 +1,50 @@
1
- import type { Selector } from ".."
1
+ import type { Atom, Selector } from ".."
2
2
  import { readOrComputeValue } from "../get-state/read-or-compute-value"
3
- import { newest } from "../lineage"
4
- import { traceAllSelectorAtoms } from "../selector"
5
3
  import type { Store } from "../store"
6
4
  import { recallState } from "./recall-state"
7
5
 
8
- export const subscribeToRootAtoms = <T>(
9
- store: Store,
10
- selector: Selector<T>,
11
- ): (() => void)[] => {
12
- const target = newest(store)
13
- const dependencySubscriptions = traceAllSelectorAtoms(selector, store).map(
14
- (atom) => {
15
- return atom.subject.subscribe(
16
- `${selector.type}:${selector.key}`,
17
- (atomChange) => {
18
- store.logger.info(
19
- `📢`,
20
- selector.type,
21
- selector.key,
22
- `root`,
23
- atom.key,
24
- `went`,
25
- atomChange.oldValue,
26
- `->`,
27
- atomChange.newValue,
28
- )
29
- const oldValue = recallState(target, selector)
30
- const newValue = readOrComputeValue(target, selector)
31
- store.logger.info(
32
- `✨`,
33
- selector.type,
34
- selector.key,
35
- `went`,
36
- oldValue,
37
- `->`,
38
- newValue,
39
- )
40
- selector.subject.next({ newValue, oldValue })
41
- },
6
+ export const subscribeToRootDependency = (
7
+ target: Store,
8
+ selector: Selector<any>,
9
+ atom: Atom<any>,
10
+ ): (() => void) => {
11
+ return atom.subject.subscribe(
12
+ `${selector.type}:${selector.key}`,
13
+ (atomChange) => {
14
+ target.logger.info(
15
+ `📢`,
16
+ selector.type,
17
+ selector.key,
18
+ `root`,
19
+ atom.key,
20
+ `went`,
21
+ atomChange.oldValue,
22
+ `->`,
23
+ atomChange.newValue,
42
24
  )
25
+ const oldValue = recallState(target, selector)
26
+ const newValue = readOrComputeValue(target, selector)
27
+ target.logger.info(
28
+ `✨`,
29
+ selector.type,
30
+ selector.key,
31
+ `went`,
32
+ oldValue,
33
+ `->`,
34
+ newValue,
35
+ )
36
+ selector.subject.next({ newValue, oldValue })
43
37
  },
44
38
  )
45
- return dependencySubscriptions
46
39
  }
40
+
41
+ // export const subscribeToRootAtoms = (
42
+ // store: Store,
43
+ // selector: Selector<any>,
44
+ // ): (() => void)[] => {
45
+ // const target = newest(store)
46
+ // const dependencySubscriptions = traceAllSelectorAtoms(selector, store).map(
47
+ // (atom) => subscribeToRootDependency(target, selector, atom),
48
+ // )
49
+ // return dependencySubscriptions
50
+ // }
@@ -1,8 +1,10 @@
1
1
  import type { ReadableToken, StateUpdate, UpdateHandler } from "atom.io"
2
2
 
3
+ import { readOrComputeValue } from "../get-state"
4
+ import { traceRootSelectorAtoms } from "../selector"
3
5
  import type { Store } from "../store"
4
6
  import { withdraw } from "../store"
5
- import { subscribeToRootAtoms } from "./subscribe-to-root-atoms"
7
+ import { subscribeToRootDependency } from "./subscribe-to-root-atoms"
6
8
 
7
9
  export function subscribeToState<T>(
8
10
  store: Store,
@@ -28,14 +30,26 @@ export function subscribeToState<T>(
28
30
  const isSelector =
29
31
  state.type === `writable_pure_selector` ||
30
32
  state.type === `readonly_pure_selector`
31
- let dependencyUnsubFunctions: (() => void)[] | null = null
33
+ const rootSubs = new Map<string, () => void>()
32
34
  let updateHandler: UpdateHandler<T> = safelyHandleUpdate
33
35
  if (isSelector) {
34
- dependencyUnsubFunctions = subscribeToRootAtoms(store, state)
35
- updateHandler = (update) => {
36
- if (dependencyUnsubFunctions) {
37
- dependencyUnsubFunctions.length = 0
38
- dependencyUnsubFunctions.push(...subscribeToRootAtoms(store, state))
36
+ readOrComputeValue(store, state)
37
+ for (const [atomKey, atom] of traceRootSelectorAtoms(store, state.key)) {
38
+ rootSubs.set(atomKey, subscribeToRootDependency(store, state, atom))
39
+ }
40
+ updateHandler = function updateRootsBeforeHandlingUpdate(update) {
41
+ const dependencies = traceRootSelectorAtoms(store, state.key)
42
+ for (const [previousRootKey, unsub] of rootSubs) {
43
+ const currentRoot = dependencies.get(previousRootKey)
44
+ if (currentRoot) {
45
+ dependencies.delete(previousRootKey)
46
+ } else {
47
+ unsub()
48
+ rootSubs.delete(previousRootKey)
49
+ }
50
+ }
51
+ for (const [atomKey, atom] of dependencies) {
52
+ rootSubs.set(atomKey, subscribeToRootDependency(store, state, atom))
39
53
  }
40
54
  safelyHandleUpdate(update)
41
55
  }
@@ -49,10 +63,8 @@ export function subscribeToState<T>(
49
63
  `Removing subscription "${key}"`,
50
64
  )
51
65
  mainUnsubFunction()
52
- if (dependencyUnsubFunctions) {
53
- for (const unsubFromDependency of dependencyUnsubFunctions) {
54
- unsubFromDependency()
55
- }
66
+ for (const unsubFromDependency of rootSubs.values()) {
67
+ unsubFromDependency()
56
68
  }
57
69
  }
58
70
 
@@ -31,7 +31,7 @@ export function useLoadable(
31
31
  | readonly [ReadableToken<any>, unknown]
32
32
  | readonly [ReadableToken<any>]
33
33
  ): `LOADING` | { loading: boolean; value: unknown } {
34
- let loadable: ReadableToken<any>
34
+ let state: unknown
35
35
  let fallback: unknown
36
36
 
37
37
  const [token] = params
@@ -43,7 +43,7 @@ export function useLoadable(
43
43
  case `readonly_pure_selector`:
44
44
  case `writable_held_selector`:
45
45
  case `writable_pure_selector`:
46
- loadable = useO(token)
46
+ state = useO(token)
47
47
  fallback = params[1]
48
48
  break
49
49
  case `atom_family`:
@@ -53,27 +53,32 @@ export function useLoadable(
53
53
  case `writable_held_selector_family`:
54
54
  case `writable_pure_selector_family`:
55
55
  key = params[1] as Canonical
56
- loadable = useO(token, key)
56
+ state = useO(token, key)
57
57
  fallback = params[2]
58
58
  }
59
59
 
60
+ const wrapperRef = React.useRef({ loading: false, value: null as unknown })
60
61
  const lastLoadedRef = React.useRef(
61
- fallback ?? (loadable instanceof Promise ? `LOADING` : loadable),
62
+ fallback ?? (state instanceof Promise ? `LOADING` : state),
62
63
  )
64
+
63
65
  const { current: lastLoaded } = lastLoadedRef
64
- if (loadable instanceof Promise) {
66
+ let { current: wrapper } = wrapperRef
67
+
68
+ if (state instanceof Promise) {
65
69
  if (lastLoaded === `LOADING`) {
66
70
  return `LOADING`
67
71
  }
68
- return {
69
- loading: true,
70
- value: lastLoaded,
72
+ wrapper = wrapperRef.current = { loading: true, value: lastLoaded }
73
+ } else {
74
+ lastLoadedRef.current = state
75
+ if (wrapper.loading === true) {
76
+ wrapper = wrapperRef.current = { loading: false, value: state }
77
+ } else {
78
+ wrapper.loading = false
79
+ wrapper.value = state
71
80
  }
72
81
  }
73
82
 
74
- lastLoadedRef.current = loadable
75
- return {
76
- loading: false,
77
- value: loadable,
78
- }
83
+ return wrapper
79
84
  }
@@ -4,12 +4,12 @@ export const optimisticUpdateQueue: AtomIO.RegularAtomToken<
4
4
  AtomIO.TransactionUpdate<any>[]
5
5
  > = AtomIO.atom<AtomIO.TransactionUpdate<any>[]>({
6
6
  key: `updateQueue`,
7
- default: [],
7
+ default: () => [],
8
8
  })
9
9
 
10
10
  export const confirmedUpdateQueue: AtomIO.RegularAtomToken<
11
11
  AtomIO.TransactionUpdate<any>[]
12
12
  > = AtomIO.atom<AtomIO.TransactionUpdate<any>[]>({
13
13
  key: `serverConfirmedUpdateQueue`,
14
- default: [],
14
+ default: () => [],
15
15
  })
@@ -192,11 +192,6 @@ export const setupRealtimeTestClient = (
192
192
  auth: { token: `test`, username: `${name}-${testNumber}` },
193
193
  })
194
194
  const silo = new AtomIO.Silo({ name, lifespan: `ephemeral` }, IMPLICIT.STORE)
195
- for (const [key, value] of silo.store.valueMap.entries()) {
196
- if (Array.isArray(value)) {
197
- silo.store.valueMap.set(key, [...value])
198
- }
199
- }
200
195
  silo.setState(RTC.myUsernameState, `${name}-${testNumber}`)
201
196
 
202
197
  const { document } = new Happy.Window()