atom.io 0.1.0 → 0.2.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 +11 -11
- package/dist/{index-3b5d305c.d.ts → index-9d9f5a05.d.ts} +66 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +206 -100
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -99
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.ts +11 -7
- package/dist/react/index.js +222 -93
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +221 -92
- package/dist/react/index.mjs.map +1 -1
- package/package.json +21 -7
- package/react/package.json +6 -0
- package/src/atom.ts +78 -0
- package/src/index.ts +91 -0
- package/src/internal/get.ts +109 -0
- package/src/internal/index.ts +23 -0
- package/src/internal/is-default.ts +19 -0
- package/src/internal/operation.ts +49 -0
- package/src/internal/selector-internal.ts +127 -0
- package/src/internal/set.ts +88 -0
- package/src/internal/store.ts +84 -0
- package/src/internal/subscribe-internal.ts +33 -0
- package/src/internal/transaction-internal.ts +34 -0
- package/src/react/index.ts +65 -0
- package/src/selector.ts +132 -0
- package/src/transaction.ts +53 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { pipe } from "fp-ts/function"
|
|
2
|
+
import HAMT from "hamt_plus"
|
|
3
|
+
|
|
4
|
+
import type { Atom, ReadonlySelector, Selector } from "."
|
|
5
|
+
import type { Store } from "./store"
|
|
6
|
+
import { IMPLICIT } from "./store"
|
|
7
|
+
import type {
|
|
8
|
+
AtomToken,
|
|
9
|
+
ReadonlyValueToken,
|
|
10
|
+
SelectorToken,
|
|
11
|
+
StateToken,
|
|
12
|
+
} from ".."
|
|
13
|
+
|
|
14
|
+
export const getCachedState = <T>(
|
|
15
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
16
|
+
store: Store = IMPLICIT.STORE
|
|
17
|
+
): T => {
|
|
18
|
+
const path = []
|
|
19
|
+
if (`default` in state) {
|
|
20
|
+
const atomKey = state.key
|
|
21
|
+
store.selectorAtoms = pipe(store.selectorAtoms, (oldValue) => {
|
|
22
|
+
let newValue = oldValue
|
|
23
|
+
for (const selectorKey of path) {
|
|
24
|
+
newValue = newValue.set(selectorKey, atomKey)
|
|
25
|
+
}
|
|
26
|
+
return newValue
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
const value = HAMT.get(state.key, store.valueMap)
|
|
30
|
+
return value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getSelectorState = <T>(
|
|
34
|
+
selector: ReadonlySelector<T> | Selector<T>
|
|
35
|
+
): T => selector.get()
|
|
36
|
+
|
|
37
|
+
export function lookup(
|
|
38
|
+
key: string,
|
|
39
|
+
store: Store
|
|
40
|
+
): AtomToken<unknown> | ReadonlyValueToken<unknown> | SelectorToken<unknown> {
|
|
41
|
+
const type = HAMT.has(key, store.atoms)
|
|
42
|
+
? `atom`
|
|
43
|
+
: HAMT.has(key, store.selectors)
|
|
44
|
+
? `selector`
|
|
45
|
+
: `readonly_selector`
|
|
46
|
+
return { key, type }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function withdraw<T>(token: AtomToken<T>, store: Store): Atom<T>
|
|
50
|
+
export function withdraw<T>(token: SelectorToken<T>, store: Store): Selector<T>
|
|
51
|
+
export function withdraw<T>(
|
|
52
|
+
token: StateToken<T>,
|
|
53
|
+
store: Store
|
|
54
|
+
): Atom<T> | Selector<T>
|
|
55
|
+
export function withdraw<T>(
|
|
56
|
+
token: ReadonlyValueToken<T>,
|
|
57
|
+
store: Store
|
|
58
|
+
): ReadonlySelector<T>
|
|
59
|
+
export function withdraw<T>(
|
|
60
|
+
token: ReadonlyValueToken<T> | StateToken<T>,
|
|
61
|
+
store: Store
|
|
62
|
+
): Atom<T> | ReadonlySelector<T> | Selector<T>
|
|
63
|
+
export function withdraw<T>(
|
|
64
|
+
token: ReadonlyValueToken<T> | StateToken<T>,
|
|
65
|
+
store: Store
|
|
66
|
+
): Atom<T> | ReadonlySelector<T> | Selector<T> {
|
|
67
|
+
return (
|
|
68
|
+
HAMT.get(token.key, store.atoms) ??
|
|
69
|
+
HAMT.get(token.key, store.selectors) ??
|
|
70
|
+
HAMT.get(token.key, store.readonlySelectors)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function deposit<T>(state: Atom<T>): AtomToken<T>
|
|
75
|
+
export function deposit<T>(state: Selector<T>): SelectorToken<T>
|
|
76
|
+
export function deposit<T>(state: Atom<T> | Selector<T>): StateToken<T>
|
|
77
|
+
export function deposit<T>(state: ReadonlySelector<T>): ReadonlyValueToken<T>
|
|
78
|
+
export function deposit<T>(
|
|
79
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>
|
|
80
|
+
): ReadonlyValueToken<T> | StateToken<T>
|
|
81
|
+
export function deposit<T>(
|
|
82
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>
|
|
83
|
+
): ReadonlyValueToken<T> | StateToken<T> {
|
|
84
|
+
if (`get` in state) {
|
|
85
|
+
if (`set` in state) {
|
|
86
|
+
return { key: state.key, type: `selector` }
|
|
87
|
+
}
|
|
88
|
+
return { key: state.key, type: `readonly_selector` }
|
|
89
|
+
}
|
|
90
|
+
return { key: state.key, type: `atom` }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const getState__INTERNAL = <T>(
|
|
94
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
95
|
+
store: Store = IMPLICIT.STORE
|
|
96
|
+
): T => {
|
|
97
|
+
if (HAMT.has(state.key, store.valueMap)) {
|
|
98
|
+
store.config.logger?.info(`>> read "${state.key}"`)
|
|
99
|
+
return getCachedState(state, store)
|
|
100
|
+
}
|
|
101
|
+
if (`get` in state) {
|
|
102
|
+
store.config.logger?.info(`-> calc "${state.key}"`)
|
|
103
|
+
return getSelectorState(state)
|
|
104
|
+
}
|
|
105
|
+
store.config.logger?.error(
|
|
106
|
+
`Attempted to get atom "${state.key}", which was never initialized in store "${store.config.name}".`
|
|
107
|
+
)
|
|
108
|
+
return state.default
|
|
109
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as Rx from "rxjs"
|
|
2
|
+
|
|
3
|
+
export * from "./get"
|
|
4
|
+
export * from "./set"
|
|
5
|
+
export * from "./is-default"
|
|
6
|
+
export * from "./selector-internal"
|
|
7
|
+
export * from "./store"
|
|
8
|
+
export * from "./subscribe-internal"
|
|
9
|
+
export * from "./operation"
|
|
10
|
+
export * from "./transaction-internal"
|
|
11
|
+
|
|
12
|
+
export type Atom<T> = {
|
|
13
|
+
key: string
|
|
14
|
+
subject: Rx.Subject<{ newValue: T; oldValue: T }>
|
|
15
|
+
default: T
|
|
16
|
+
}
|
|
17
|
+
export type Selector<T> = {
|
|
18
|
+
key: string
|
|
19
|
+
subject: Rx.Subject<{ newValue: T; oldValue: T }>
|
|
20
|
+
get: () => T
|
|
21
|
+
set: (newValue: T | ((oldValue: T) => T)) => void
|
|
22
|
+
}
|
|
23
|
+
export type ReadonlySelector<T> = Omit<Selector<T>, `set`>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
|
|
3
|
+
import type { Store } from "."
|
|
4
|
+
import { IMPLICIT, traceAllSelectorAtoms } from "."
|
|
5
|
+
|
|
6
|
+
export const isAtomDefault = (
|
|
7
|
+
key: string,
|
|
8
|
+
store: Store = IMPLICIT.STORE
|
|
9
|
+
): boolean => {
|
|
10
|
+
return HAMT.get(key, store.atomsAreDefault)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const isSelectorDefault = (
|
|
14
|
+
key: string,
|
|
15
|
+
store: Store = IMPLICIT.STORE
|
|
16
|
+
): boolean => {
|
|
17
|
+
const roots = traceAllSelectorAtoms(key, store)
|
|
18
|
+
return roots.every((root) => isAtomDefault(root.key, store))
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
|
|
3
|
+
import type { Atom, ReadonlySelector, Selector } from "."
|
|
4
|
+
import type { Store } from "./store"
|
|
5
|
+
import { IMPLICIT } from "./store"
|
|
6
|
+
|
|
7
|
+
export const startAction = (store: Store): void => {
|
|
8
|
+
store.operation = {
|
|
9
|
+
open: true,
|
|
10
|
+
done: new Set(),
|
|
11
|
+
prev: store.valueMap,
|
|
12
|
+
}
|
|
13
|
+
store.config.logger?.info(`⭕`, `operation start`)
|
|
14
|
+
}
|
|
15
|
+
export const finishAction = (store: Store): void => {
|
|
16
|
+
store.operation = { open: false }
|
|
17
|
+
store.config.logger?.info(`🔴`, `operation done`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const isDone = (key: string, store: Store = IMPLICIT.STORE): boolean => {
|
|
21
|
+
if (!store.operation.open) {
|
|
22
|
+
store.config.logger?.warn(
|
|
23
|
+
`isDone called outside of an action. This is probably a bug.`
|
|
24
|
+
)
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
return store.operation.done.has(key)
|
|
28
|
+
}
|
|
29
|
+
export const markDone = (key: string, store: Store = IMPLICIT.STORE): void => {
|
|
30
|
+
if (!store.operation.open) {
|
|
31
|
+
store.config.logger?.warn(
|
|
32
|
+
`markDone called outside of an action. This is probably a bug.`
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
store.operation.done.add(key)
|
|
37
|
+
}
|
|
38
|
+
export const recallState = <T>(
|
|
39
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
40
|
+
store: Store = IMPLICIT.STORE
|
|
41
|
+
): T => {
|
|
42
|
+
if (!store.operation.open) {
|
|
43
|
+
store.config.logger?.warn(
|
|
44
|
+
`recall called outside of an action. This is probably a bug.`
|
|
45
|
+
)
|
|
46
|
+
return HAMT.get(state.key, store.valueMap)
|
|
47
|
+
}
|
|
48
|
+
return HAMT.get(state.key, store.operation.prev)
|
|
49
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Store } from "."
|
|
2
|
+
import {
|
|
3
|
+
lookup,
|
|
4
|
+
IMPLICIT,
|
|
5
|
+
getState__INTERNAL,
|
|
6
|
+
setState__INTERNAL,
|
|
7
|
+
withdraw,
|
|
8
|
+
} from "."
|
|
9
|
+
import type {
|
|
10
|
+
AtomToken,
|
|
11
|
+
ReadonlyValueToken,
|
|
12
|
+
SelectorToken,
|
|
13
|
+
StateToken,
|
|
14
|
+
} from ".."
|
|
15
|
+
import type { Transactors } from "../transaction"
|
|
16
|
+
|
|
17
|
+
export const lookupSelectorSources = (
|
|
18
|
+
key: string,
|
|
19
|
+
store: Store
|
|
20
|
+
): (
|
|
21
|
+
| AtomToken<unknown>
|
|
22
|
+
| ReadonlyValueToken<unknown>
|
|
23
|
+
| SelectorToken<unknown>
|
|
24
|
+
)[] =>
|
|
25
|
+
store.selectorGraph
|
|
26
|
+
.getRelations(key)
|
|
27
|
+
.filter(({ source }) => source !== key)
|
|
28
|
+
.map(({ source }) => lookup(source, store))
|
|
29
|
+
|
|
30
|
+
export const traceSelectorAtoms = (
|
|
31
|
+
selectorKey: string,
|
|
32
|
+
dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
|
|
33
|
+
store: Store
|
|
34
|
+
): AtomToken<unknown>[] => {
|
|
35
|
+
const roots: AtomToken<unknown>[] = []
|
|
36
|
+
|
|
37
|
+
const sources = lookupSelectorSources(dependency.key, store)
|
|
38
|
+
let depth = 0
|
|
39
|
+
while (sources.length > 0) {
|
|
40
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
|
41
|
+
const source = sources.shift()!
|
|
42
|
+
++depth
|
|
43
|
+
if (depth > 999) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Maximum selector dependency depth exceeded in selector "${selectorKey}".`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (source.type !== `atom`) {
|
|
50
|
+
sources.push(...lookupSelectorSources(source.key, store))
|
|
51
|
+
} else {
|
|
52
|
+
roots.push(source)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return roots
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const traceAllSelectorAtoms = (
|
|
60
|
+
selectorKey: string,
|
|
61
|
+
store: Store
|
|
62
|
+
): AtomToken<unknown>[] => {
|
|
63
|
+
const sources = lookupSelectorSources(selectorKey, store)
|
|
64
|
+
return sources.flatMap((source) =>
|
|
65
|
+
source.type === `atom`
|
|
66
|
+
? source
|
|
67
|
+
: traceSelectorAtoms(selectorKey, source, store)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const updateSelectorAtoms = (
|
|
72
|
+
selectorKey: string,
|
|
73
|
+
dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
|
|
74
|
+
store: Store
|
|
75
|
+
): void => {
|
|
76
|
+
if (dependency.type === `atom`) {
|
|
77
|
+
store.selectorAtoms = store.selectorAtoms.set(selectorKey, dependency.key)
|
|
78
|
+
store.config.logger?.info(
|
|
79
|
+
` || adding root for "${selectorKey}": ${dependency.key}`
|
|
80
|
+
)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
const roots = traceSelectorAtoms(selectorKey, dependency, store)
|
|
84
|
+
store.config.logger?.info(` || adding roots for "${selectorKey}":`, roots)
|
|
85
|
+
for (const root of roots) {
|
|
86
|
+
store.selectorAtoms = store.selectorAtoms.set(selectorKey, root.key)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const registerSelector = (
|
|
91
|
+
selectorKey: string,
|
|
92
|
+
store: Store = IMPLICIT.STORE
|
|
93
|
+
): Transactors => ({
|
|
94
|
+
get: (dependency) => {
|
|
95
|
+
const alreadyRegistered = store.selectorGraph
|
|
96
|
+
.getRelations(selectorKey)
|
|
97
|
+
.some(({ source }) => source === dependency.key)
|
|
98
|
+
|
|
99
|
+
const dependencyState = withdraw(dependency, store)
|
|
100
|
+
const dependencyValue = getState__INTERNAL(dependencyState, store)
|
|
101
|
+
|
|
102
|
+
if (alreadyRegistered) {
|
|
103
|
+
store.config.logger?.info(
|
|
104
|
+
` || ${selectorKey} <- ${dependency.key} =`,
|
|
105
|
+
dependencyValue
|
|
106
|
+
)
|
|
107
|
+
} else {
|
|
108
|
+
store.config.logger?.info(
|
|
109
|
+
`🔌 registerSelector "${selectorKey}" <- "${dependency.key}" =`,
|
|
110
|
+
dependencyValue
|
|
111
|
+
)
|
|
112
|
+
store.selectorGraph = store.selectorGraph.set(
|
|
113
|
+
selectorKey,
|
|
114
|
+
dependency.key,
|
|
115
|
+
{
|
|
116
|
+
source: dependency.key,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
updateSelectorAtoms(selectorKey, dependency, store)
|
|
121
|
+
return dependencyValue
|
|
122
|
+
},
|
|
123
|
+
set: (stateToken, newValue) => {
|
|
124
|
+
const state = withdraw(stateToken, store)
|
|
125
|
+
setState__INTERNAL(state, newValue, store)
|
|
126
|
+
},
|
|
127
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
|
|
3
|
+
import { become } from "~/packages/anvl/src/function"
|
|
4
|
+
|
|
5
|
+
import type { Atom, Selector } from "."
|
|
6
|
+
import { isAtomDefault } from "."
|
|
7
|
+
import { getState__INTERNAL } from "./get"
|
|
8
|
+
import { isDone, markDone } from "./operation"
|
|
9
|
+
import type { Store } from "./store"
|
|
10
|
+
import { IMPLICIT } from "./store"
|
|
11
|
+
|
|
12
|
+
export const evictDownStream = <T>(
|
|
13
|
+
state: Atom<T>,
|
|
14
|
+
store: Store = IMPLICIT.STORE
|
|
15
|
+
): void => {
|
|
16
|
+
const downstream = store.selectorAtoms.getRelations(state.key)
|
|
17
|
+
const downstreamKeys = downstream.map(({ id }) => id)
|
|
18
|
+
store.config.logger?.info(
|
|
19
|
+
` || ${downstreamKeys.length} downstream:`,
|
|
20
|
+
downstreamKeys
|
|
21
|
+
)
|
|
22
|
+
if (store.operation.open) {
|
|
23
|
+
store.config.logger?.info(` ||`, [...store.operation.done], `already done`)
|
|
24
|
+
}
|
|
25
|
+
downstream.forEach(({ id: stateKey }) => {
|
|
26
|
+
if (isDone(stateKey, store)) {
|
|
27
|
+
store.config.logger?.info(` || ${stateKey} already done`)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
const state =
|
|
31
|
+
HAMT.get(stateKey, store.selectors) ??
|
|
32
|
+
HAMT.get(stateKey, store.readonlySelectors)
|
|
33
|
+
if (!state) {
|
|
34
|
+
store.config.logger?.info(
|
|
35
|
+
` || ${stateKey} is an atom, and can't be downstream`
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
store.valueMap = HAMT.remove(stateKey, store.valueMap)
|
|
40
|
+
store.config.logger?.info(` xx evicted "${stateKey}"`)
|
|
41
|
+
|
|
42
|
+
markDone(stateKey, store)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const setAtomState = <T>(
|
|
47
|
+
atom: Atom<T>,
|
|
48
|
+
next: T | ((oldValue: T) => T),
|
|
49
|
+
store: Store = IMPLICIT.STORE
|
|
50
|
+
): void => {
|
|
51
|
+
const oldValue = getState__INTERNAL(atom, store)
|
|
52
|
+
const newValue = become(next)(oldValue)
|
|
53
|
+
store.config.logger?.info(`-> setting atom "${atom.key}" to`, newValue)
|
|
54
|
+
store.valueMap = HAMT.set(atom.key, newValue, store.valueMap)
|
|
55
|
+
if (isAtomDefault(atom.key)) {
|
|
56
|
+
store.atomsAreDefault = HAMT.set(atom.key, false, store.atomsAreDefault)
|
|
57
|
+
}
|
|
58
|
+
markDone(atom.key, store)
|
|
59
|
+
store.config.logger?.info(
|
|
60
|
+
` || evicting caches downstream from "${atom.key}"`
|
|
61
|
+
)
|
|
62
|
+
evictDownStream(atom, store)
|
|
63
|
+
atom.subject.next({ newValue, oldValue })
|
|
64
|
+
}
|
|
65
|
+
export const setSelectorState = <T>(
|
|
66
|
+
selector: Selector<T>,
|
|
67
|
+
next: T | ((oldValue: T) => T),
|
|
68
|
+
store: Store = IMPLICIT.STORE
|
|
69
|
+
): void => {
|
|
70
|
+
const oldValue = getState__INTERNAL(selector, store)
|
|
71
|
+
const newValue = become(next)(oldValue)
|
|
72
|
+
|
|
73
|
+
store.config.logger?.info(`-> setting selector "${selector.key}" to`, newValue)
|
|
74
|
+
store.config.logger?.info(` || propagating change made to "${selector.key}"`)
|
|
75
|
+
|
|
76
|
+
selector.set(newValue)
|
|
77
|
+
}
|
|
78
|
+
export const setState__INTERNAL = <T>(
|
|
79
|
+
state: Atom<T> | Selector<T>,
|
|
80
|
+
value: T | ((oldValue: T) => T),
|
|
81
|
+
store: Store = IMPLICIT.STORE
|
|
82
|
+
): void => {
|
|
83
|
+
if (`set` in state) {
|
|
84
|
+
setSelectorState(state, value, store)
|
|
85
|
+
} else {
|
|
86
|
+
setAtomState(state, value, store)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Hamt } from "hamt_plus"
|
|
2
|
+
import HAMT from "hamt_plus"
|
|
3
|
+
|
|
4
|
+
import { Join } from "~/packages/anvl/src/join"
|
|
5
|
+
|
|
6
|
+
import type { Atom, ReadonlySelector, Selector } from "."
|
|
7
|
+
|
|
8
|
+
export interface Store {
|
|
9
|
+
valueMap: Hamt<any, string>
|
|
10
|
+
selectorGraph: Join<{ source: string }>
|
|
11
|
+
selectorAtoms: Join
|
|
12
|
+
atoms: Hamt<Atom<any>, string>
|
|
13
|
+
atomsAreDefault: Hamt<boolean, string>
|
|
14
|
+
selectors: Hamt<Selector<any>, string>
|
|
15
|
+
readonlySelectors: Hamt<ReadonlySelector<any>, string>
|
|
16
|
+
operation:
|
|
17
|
+
| {
|
|
18
|
+
open: false
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
open: true
|
|
22
|
+
done: Set<string>
|
|
23
|
+
prev: Hamt<any, string>
|
|
24
|
+
}
|
|
25
|
+
transaction:
|
|
26
|
+
| {
|
|
27
|
+
open: false
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
open: true
|
|
31
|
+
prev: Pick<
|
|
32
|
+
Store,
|
|
33
|
+
| `atoms`
|
|
34
|
+
| `readonlySelectors`
|
|
35
|
+
| `selectorGraph`
|
|
36
|
+
| `selectors`
|
|
37
|
+
| `valueMap`
|
|
38
|
+
>
|
|
39
|
+
}
|
|
40
|
+
config: {
|
|
41
|
+
name: string
|
|
42
|
+
logger: Pick<Console, `error` | `info` | `warn`> | null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const createStore = (name: string): Store =>
|
|
47
|
+
({
|
|
48
|
+
valueMap: HAMT.make<any, string>(),
|
|
49
|
+
selectorGraph: new Join({ relationType: `n:n` }),
|
|
50
|
+
selectorAtoms: new Join({ relationType: `n:n` }),
|
|
51
|
+
atoms: HAMT.make<Atom<any>, string>(),
|
|
52
|
+
atomsAreDefault: HAMT.make<boolean, string>(),
|
|
53
|
+
selectors: HAMT.make<Selector<any>, string>(),
|
|
54
|
+
readonlySelectors: HAMT.make<ReadonlySelector<any>, string>(),
|
|
55
|
+
operation: {
|
|
56
|
+
open: false,
|
|
57
|
+
},
|
|
58
|
+
transaction: {
|
|
59
|
+
open: false,
|
|
60
|
+
},
|
|
61
|
+
config: {
|
|
62
|
+
name,
|
|
63
|
+
logger: null,
|
|
64
|
+
},
|
|
65
|
+
} satisfies Store)
|
|
66
|
+
|
|
67
|
+
export const IMPLICIT = {
|
|
68
|
+
STORE_INTERNAL: undefined as Store | undefined,
|
|
69
|
+
get STORE(): Store {
|
|
70
|
+
return this.STORE_INTERNAL ?? (this.STORE_INTERNAL = createStore(`DEFAULT`))
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
export const configure = (
|
|
74
|
+
config: Partial<Store[`config`]>,
|
|
75
|
+
store: Store = IMPLICIT.STORE
|
|
76
|
+
): void => {
|
|
77
|
+
Object.assign(store.config, config)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const clearStore = (store: Store = IMPLICIT.STORE): void => {
|
|
81
|
+
const { config } = store
|
|
82
|
+
Object.assign(store, createStore(config.name))
|
|
83
|
+
store.config = config
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Atom, ReadonlySelector, Selector } from "."
|
|
2
|
+
import { getState__INTERNAL, withdraw } from "./get"
|
|
3
|
+
import { recallState } from "./operation"
|
|
4
|
+
import { traceAllSelectorAtoms } from "./selector-internal"
|
|
5
|
+
import type { Store } from "./store"
|
|
6
|
+
import { IMPLICIT } from "./store"
|
|
7
|
+
import { __INTERNAL__ } from ".."
|
|
8
|
+
|
|
9
|
+
export const subscribeToRootAtoms = <T>(
|
|
10
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
11
|
+
store: Store = IMPLICIT.STORE
|
|
12
|
+
): { unsubscribe: () => void }[] | null => {
|
|
13
|
+
const dependencySubscriptions =
|
|
14
|
+
`default` in state
|
|
15
|
+
? null
|
|
16
|
+
: traceAllSelectorAtoms(state.key, store).map((atomToken) => {
|
|
17
|
+
const atom = withdraw(atomToken, store)
|
|
18
|
+
return atom.subject.subscribe((atomChange) => {
|
|
19
|
+
store.config.logger?.info(
|
|
20
|
+
`📢 atom changed: "${atomToken.key}" (`,
|
|
21
|
+
atomChange.oldValue,
|
|
22
|
+
`->`,
|
|
23
|
+
atomChange.newValue,
|
|
24
|
+
`) re-evaluating "${state.key}"`
|
|
25
|
+
)
|
|
26
|
+
const oldValue = recallState(state, store)
|
|
27
|
+
const newValue = getState__INTERNAL(state, store)
|
|
28
|
+
store.config.logger?.info(` <- ${state.key} became`, newValue)
|
|
29
|
+
state.subject.next({ newValue, oldValue })
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
return dependencySubscriptions
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Store } from "./store"
|
|
2
|
+
|
|
3
|
+
export const finishTransaction = (store: Store): void => {
|
|
4
|
+
store.transaction = { open: false }
|
|
5
|
+
store.config.logger?.info(`🛬`, `transaction done`)
|
|
6
|
+
}
|
|
7
|
+
export const startTransaction = (store: Store): void => {
|
|
8
|
+
store.transaction = {
|
|
9
|
+
open: true,
|
|
10
|
+
prev: {
|
|
11
|
+
atoms: store.atoms,
|
|
12
|
+
readonlySelectors: store.readonlySelectors,
|
|
13
|
+
selectorGraph: store.selectorGraph,
|
|
14
|
+
selectors: store.selectors,
|
|
15
|
+
valueMap: store.valueMap,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
store.config.logger?.info(`🛫`, `transaction start`)
|
|
19
|
+
}
|
|
20
|
+
export const abortTransaction = (store: Store): void => {
|
|
21
|
+
if (!store.transaction.open) {
|
|
22
|
+
store.config.logger?.warn(
|
|
23
|
+
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
24
|
+
)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
store.atoms = store.transaction.prev.atoms
|
|
28
|
+
store.readonlySelectors = store.transaction.prev.readonlySelectors
|
|
29
|
+
store.selectorGraph = store.transaction.prev.selectorGraph
|
|
30
|
+
store.selectors = store.transaction.prev.selectors
|
|
31
|
+
store.valueMap = store.transaction.prev.valueMap
|
|
32
|
+
store.transaction = { open: false }
|
|
33
|
+
store.config.logger?.info(`🪂`, `transaction fail`)
|
|
34
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type Preact from "preact/hooks"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
|
|
5
|
+
import type { Modifier } from "~/packages/anvl/src/function"
|
|
6
|
+
|
|
7
|
+
import type { ReadonlyValueToken, StateToken } from ".."
|
|
8
|
+
import { subscribe, setState, __INTERNAL__ } from ".."
|
|
9
|
+
import { withdraw } from "../internal"
|
|
10
|
+
|
|
11
|
+
export type AtomStoreReactConfig = {
|
|
12
|
+
useState: typeof Preact.useState | typeof React.useState
|
|
13
|
+
useEffect: typeof Preact.useEffect | typeof React.useEffect
|
|
14
|
+
store?: __INTERNAL__.Store
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
18
|
+
export const composeStoreHooks = ({
|
|
19
|
+
useState,
|
|
20
|
+
useEffect,
|
|
21
|
+
store = __INTERNAL__.IMPLICIT.STORE,
|
|
22
|
+
}: AtomStoreReactConfig) => {
|
|
23
|
+
function useI<T>(token: StateToken<T>): (next: Modifier<T> | T) => void {
|
|
24
|
+
const updateState = (next: Modifier<T> | T) => setState(token, next, store)
|
|
25
|
+
return updateState
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function useO<T>(token: ReadonlyValueToken<T> | StateToken<T>): T {
|
|
29
|
+
const state = withdraw(token, store)
|
|
30
|
+
const initialValue = __INTERNAL__.getState__INTERNAL(state, store)
|
|
31
|
+
const [current, dispatch] = useState(initialValue)
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const unsubscribe = subscribe(
|
|
34
|
+
token,
|
|
35
|
+
({ newValue, oldValue }) => {
|
|
36
|
+
if (oldValue !== newValue) {
|
|
37
|
+
dispatch(newValue)
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
store
|
|
41
|
+
)
|
|
42
|
+
return unsubscribe
|
|
43
|
+
}, [current, dispatch])
|
|
44
|
+
|
|
45
|
+
return current
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function useIO<T>(token: StateToken<T>): [T, (next: Modifier<T> | T) => void] {
|
|
49
|
+
return [useO(token), useI(token)]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function useStore<T>(
|
|
53
|
+
token: StateToken<T>
|
|
54
|
+
): [T, (next: Modifier<T> | T) => void]
|
|
55
|
+
function useStore<T>(token: ReadonlyValueToken<T>): T
|
|
56
|
+
function useStore<T>(
|
|
57
|
+
token: ReadonlyValueToken<T> | StateToken<T>
|
|
58
|
+
): T | [T, (next: Modifier<T> | T) => void] {
|
|
59
|
+
if (token.type === `readonly_selector`) {
|
|
60
|
+
return useO(token)
|
|
61
|
+
}
|
|
62
|
+
return useIO(token)
|
|
63
|
+
}
|
|
64
|
+
return { useI, useO, useIO, useStore }
|
|
65
|
+
}
|