atom.io 0.1.0 → 0.3.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 +10 -15
- package/dist/index.d.ts +505 -4
- package/dist/index.js +890 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +879 -316
- package/dist/index.mjs.map +1 -1
- package/package.json +42 -10
- package/react/dist/index.d.ts +22 -0
- package/react/dist/index.js +68 -0
- package/react/dist/index.js.map +1 -0
- package/react/dist/index.mjs +44 -0
- package/react/dist/index.mjs.map +1 -0
- package/react/package.json +15 -0
- package/src/atom.ts +43 -0
- package/src/index.ts +77 -0
- package/src/internal/atom-internal.ts +50 -0
- package/src/internal/families-internal.ts +142 -0
- package/src/internal/get.ts +107 -0
- package/src/internal/index.ts +12 -0
- package/src/internal/is-default.ts +35 -0
- package/src/internal/logger.ts +46 -0
- package/src/internal/operation.ts +132 -0
- package/src/internal/selector-internal.ts +227 -0
- package/src/internal/set.ts +102 -0
- package/src/internal/store.ts +97 -0
- package/src/internal/subscribe-internal.ts +77 -0
- package/src/internal/timeline-internal.ts +196 -0
- package/src/internal/transaction-internal.ts +175 -0
- package/src/react/index.ts +64 -0
- package/src/selector.ts +62 -0
- package/src/subscribe.ts +55 -0
- package/src/timeline.ts +34 -0
- package/src/transaction.ts +41 -0
- package/dist/index-3b5d305c.d.ts +0 -257
- package/dist/react/index.d.ts +0 -21
- package/dist/react/index.js +0 -780
- package/dist/react/index.js.map +0 -1
- package/dist/react/index.mjs +0 -751
- package/dist/react/index.mjs.map +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Hamt } from "hamt_plus"
|
|
2
|
+
import HAMT from "hamt_plus"
|
|
3
|
+
|
|
4
|
+
import type { Atom, ReadonlySelector, Selector } from "."
|
|
5
|
+
import { target } from "."
|
|
6
|
+
import type { Store } from "./store"
|
|
7
|
+
import { IMPLICIT } from "./store"
|
|
8
|
+
|
|
9
|
+
export type OperationProgress =
|
|
10
|
+
| {
|
|
11
|
+
open: false
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
open: true
|
|
15
|
+
done: Set<string>
|
|
16
|
+
prev: Hamt<any, string>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const openOperation = (store: Store): void => {
|
|
20
|
+
const core = target(store)
|
|
21
|
+
core.operation = {
|
|
22
|
+
open: true,
|
|
23
|
+
done: new Set(),
|
|
24
|
+
prev: store.valueMap,
|
|
25
|
+
}
|
|
26
|
+
store.config.logger?.info(`⭕`, `operation start`)
|
|
27
|
+
}
|
|
28
|
+
export const closeOperation = (store: Store): void => {
|
|
29
|
+
const core = target(store)
|
|
30
|
+
core.operation = { open: false }
|
|
31
|
+
store.config.logger?.info(`🔴`, `operation done`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const isDone = (key: string, store: Store = IMPLICIT.STORE): boolean => {
|
|
35
|
+
const core = target(store)
|
|
36
|
+
if (!core.operation.open) {
|
|
37
|
+
store.config.logger?.warn(
|
|
38
|
+
`isDone called outside of an operation. This is probably a bug.`
|
|
39
|
+
)
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return core.operation.done.has(key)
|
|
43
|
+
}
|
|
44
|
+
export const markDone = (key: string, store: Store = IMPLICIT.STORE): void => {
|
|
45
|
+
const core = target(store)
|
|
46
|
+
if (!core.operation.open) {
|
|
47
|
+
store.config.logger?.warn(
|
|
48
|
+
`markDone called outside of an operation. This is probably a bug.`
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
core.operation.done.add(key)
|
|
53
|
+
}
|
|
54
|
+
export const recallState = <T>(
|
|
55
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
56
|
+
store: Store = IMPLICIT.STORE
|
|
57
|
+
): T => {
|
|
58
|
+
const core = target(store)
|
|
59
|
+
if (!core.operation.open) {
|
|
60
|
+
store.config.logger?.warn(
|
|
61
|
+
`recall called outside of an operation. This is probably a bug.`
|
|
62
|
+
)
|
|
63
|
+
return HAMT.get(state.key, core.valueMap)
|
|
64
|
+
}
|
|
65
|
+
return HAMT.get(state.key, core.operation.prev)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const cacheValue = (
|
|
69
|
+
key: string,
|
|
70
|
+
value: unknown,
|
|
71
|
+
store: Store = IMPLICIT.STORE
|
|
72
|
+
): void => {
|
|
73
|
+
const core = target(store)
|
|
74
|
+
core.valueMap = HAMT.set(key, value, core.valueMap)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const evictCachedValue = (
|
|
78
|
+
key: string,
|
|
79
|
+
store: Store = IMPLICIT.STORE
|
|
80
|
+
): void => {
|
|
81
|
+
const core = target(store)
|
|
82
|
+
core.valueMap = HAMT.remove(key, core.valueMap)
|
|
83
|
+
}
|
|
84
|
+
export const readCachedValue = <T>(
|
|
85
|
+
key: string,
|
|
86
|
+
store: Store = IMPLICIT.STORE
|
|
87
|
+
): T => HAMT.get(key, target(store).valueMap)
|
|
88
|
+
|
|
89
|
+
export const isValueCached = (
|
|
90
|
+
key: string,
|
|
91
|
+
store: Store = IMPLICIT.STORE
|
|
92
|
+
): boolean => HAMT.has(key, target(store).valueMap)
|
|
93
|
+
|
|
94
|
+
export const storeAtom = (
|
|
95
|
+
atom: Atom<any>,
|
|
96
|
+
store: Store = IMPLICIT.STORE
|
|
97
|
+
): void => {
|
|
98
|
+
const core = target(store)
|
|
99
|
+
core.atoms = HAMT.set(atom.key, atom, core.atoms)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const storeSelector = (
|
|
103
|
+
selector: Selector<any>,
|
|
104
|
+
store: Store = IMPLICIT.STORE
|
|
105
|
+
): void => {
|
|
106
|
+
const core = target(store)
|
|
107
|
+
core.selectors = HAMT.set(selector.key, selector, core.selectors)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const storeReadonlySelector = (
|
|
111
|
+
selector: ReadonlySelector<any>,
|
|
112
|
+
store: Store = IMPLICIT.STORE
|
|
113
|
+
): void => {
|
|
114
|
+
const core = target(store)
|
|
115
|
+
core.readonlySelectors = HAMT.set(
|
|
116
|
+
selector.key,
|
|
117
|
+
selector,
|
|
118
|
+
core.readonlySelectors
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const hasKeyBeenUsed = (
|
|
123
|
+
key: string,
|
|
124
|
+
store: Store = IMPLICIT.STORE
|
|
125
|
+
): boolean => {
|
|
126
|
+
const core = target(store)
|
|
127
|
+
return (
|
|
128
|
+
HAMT.has(key, core.atoms) ||
|
|
129
|
+
HAMT.has(key, core.selectors) ||
|
|
130
|
+
HAMT.has(key, core.readonlySelectors)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
import * as Rx from "rxjs"
|
|
3
|
+
|
|
4
|
+
import { become } from "~/packages/anvl/src/function"
|
|
5
|
+
|
|
6
|
+
import type { Store } from "."
|
|
7
|
+
import {
|
|
8
|
+
target,
|
|
9
|
+
cacheValue,
|
|
10
|
+
markDone,
|
|
11
|
+
lookup,
|
|
12
|
+
IMPLICIT,
|
|
13
|
+
getState__INTERNAL,
|
|
14
|
+
setState__INTERNAL,
|
|
15
|
+
withdraw,
|
|
16
|
+
} from "."
|
|
17
|
+
import type {
|
|
18
|
+
AtomToken,
|
|
19
|
+
FamilyMetadata,
|
|
20
|
+
ReadonlySelectorOptions,
|
|
21
|
+
ReadonlyValueToken,
|
|
22
|
+
SelectorOptions,
|
|
23
|
+
SelectorToken,
|
|
24
|
+
StateToken,
|
|
25
|
+
} from ".."
|
|
26
|
+
import type { Transactors } from "../transaction"
|
|
27
|
+
|
|
28
|
+
export type Selector<T> = {
|
|
29
|
+
key: string
|
|
30
|
+
type: `selector`
|
|
31
|
+
family?: FamilyMetadata
|
|
32
|
+
subject: Rx.Subject<{ newValue: T; oldValue: T }>
|
|
33
|
+
get: () => T
|
|
34
|
+
set: (newValue: T | ((oldValue: T) => T)) => void
|
|
35
|
+
}
|
|
36
|
+
export type ReadonlySelector<T> = {
|
|
37
|
+
key: string
|
|
38
|
+
type: `readonly_selector`
|
|
39
|
+
family?: FamilyMetadata
|
|
40
|
+
subject: Rx.Subject<{ newValue: T; oldValue: T }>
|
|
41
|
+
get: () => T
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const lookupSelectorSources = (
|
|
45
|
+
key: string,
|
|
46
|
+
store: Store
|
|
47
|
+
): (
|
|
48
|
+
| AtomToken<unknown>
|
|
49
|
+
| ReadonlyValueToken<unknown>
|
|
50
|
+
| SelectorToken<unknown>
|
|
51
|
+
)[] =>
|
|
52
|
+
target(store)
|
|
53
|
+
.selectorGraph.getRelations(key)
|
|
54
|
+
.filter(({ source }) => source !== key)
|
|
55
|
+
.map(({ source }) => lookup(source, store))
|
|
56
|
+
|
|
57
|
+
export const traceSelectorAtoms = (
|
|
58
|
+
selectorKey: string,
|
|
59
|
+
dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
|
|
60
|
+
store: Store
|
|
61
|
+
): AtomToken<unknown>[] => {
|
|
62
|
+
const roots: AtomToken<unknown>[] = []
|
|
63
|
+
|
|
64
|
+
const sources = lookupSelectorSources(dependency.key, store)
|
|
65
|
+
let depth = 0
|
|
66
|
+
while (sources.length > 0) {
|
|
67
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
|
68
|
+
const source = sources.shift()!
|
|
69
|
+
++depth
|
|
70
|
+
if (depth > 999) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Maximum selector dependency depth exceeded in selector "${selectorKey}".`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (source.type !== `atom`) {
|
|
77
|
+
sources.push(...lookupSelectorSources(source.key, store))
|
|
78
|
+
} else {
|
|
79
|
+
roots.push(source)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return roots
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const traceAllSelectorAtoms = (
|
|
87
|
+
selectorKey: string,
|
|
88
|
+
store: Store
|
|
89
|
+
): AtomToken<unknown>[] => {
|
|
90
|
+
const sources = lookupSelectorSources(selectorKey, store)
|
|
91
|
+
return sources.flatMap((source) =>
|
|
92
|
+
source.type === `atom`
|
|
93
|
+
? source
|
|
94
|
+
: traceSelectorAtoms(selectorKey, source, store)
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const updateSelectorAtoms = (
|
|
99
|
+
selectorKey: string,
|
|
100
|
+
dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
|
|
101
|
+
store: Store
|
|
102
|
+
): void => {
|
|
103
|
+
const core = target(store)
|
|
104
|
+
if (dependency.type === `atom`) {
|
|
105
|
+
core.selectorAtoms = core.selectorAtoms.set(selectorKey, dependency.key)
|
|
106
|
+
store.config.logger?.info(
|
|
107
|
+
` || adding root for "${selectorKey}": ${dependency.key}`
|
|
108
|
+
)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
const roots = traceSelectorAtoms(selectorKey, dependency, store)
|
|
112
|
+
store.config.logger?.info(
|
|
113
|
+
` || adding roots for "${selectorKey}":`,
|
|
114
|
+
roots.map((r) => r.key)
|
|
115
|
+
)
|
|
116
|
+
for (const root of roots) {
|
|
117
|
+
core.selectorAtoms = core.selectorAtoms.set(selectorKey, root.key)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const registerSelector = (
|
|
122
|
+
selectorKey: string,
|
|
123
|
+
store: Store = IMPLICIT.STORE
|
|
124
|
+
): Transactors => ({
|
|
125
|
+
get: (dependency) => {
|
|
126
|
+
const core = target(store)
|
|
127
|
+
const alreadyRegistered = core.selectorGraph
|
|
128
|
+
.getRelations(selectorKey)
|
|
129
|
+
.some(({ source }) => source === dependency.key)
|
|
130
|
+
|
|
131
|
+
const dependencyState = withdraw(dependency, store)
|
|
132
|
+
const dependencyValue = getState__INTERNAL(dependencyState, store)
|
|
133
|
+
|
|
134
|
+
if (alreadyRegistered) {
|
|
135
|
+
store.config.logger?.info(
|
|
136
|
+
` || ${selectorKey} <- ${dependency.key} =`,
|
|
137
|
+
dependencyValue
|
|
138
|
+
)
|
|
139
|
+
} else {
|
|
140
|
+
store.config.logger?.info(
|
|
141
|
+
`🔌 registerSelector "${selectorKey}" <- "${dependency.key}" =`,
|
|
142
|
+
dependencyValue
|
|
143
|
+
)
|
|
144
|
+
core.selectorGraph = core.selectorGraph.set(selectorKey, dependency.key, {
|
|
145
|
+
source: dependency.key,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
updateSelectorAtoms(selectorKey, dependency, store)
|
|
149
|
+
return dependencyValue
|
|
150
|
+
},
|
|
151
|
+
set: (stateToken, newValue) => {
|
|
152
|
+
const state = withdraw(stateToken, store)
|
|
153
|
+
setState__INTERNAL(state, newValue, store)
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
export function selector__INTERNAL<T>(
|
|
158
|
+
options: SelectorOptions<T>,
|
|
159
|
+
family?: FamilyMetadata,
|
|
160
|
+
store?: Store
|
|
161
|
+
): SelectorToken<T>
|
|
162
|
+
export function selector__INTERNAL<T>(
|
|
163
|
+
options: ReadonlySelectorOptions<T>,
|
|
164
|
+
family?: FamilyMetadata,
|
|
165
|
+
store?: Store
|
|
166
|
+
): ReadonlyValueToken<T>
|
|
167
|
+
export function selector__INTERNAL<T>(
|
|
168
|
+
options: ReadonlySelectorOptions<T> | SelectorOptions<T>,
|
|
169
|
+
family?: FamilyMetadata,
|
|
170
|
+
store: Store = IMPLICIT.STORE
|
|
171
|
+
): ReadonlyValueToken<T> | SelectorToken<T> {
|
|
172
|
+
const core = target(store)
|
|
173
|
+
if (HAMT.has(options.key, core.selectors)) {
|
|
174
|
+
store.config.logger?.error(
|
|
175
|
+
`Key "${options.key}" already exists in the store.`
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const subject = new Rx.Subject<{ newValue: T; oldValue: T }>()
|
|
180
|
+
|
|
181
|
+
const { get, set } = registerSelector(options.key, store)
|
|
182
|
+
const getSelf = () => {
|
|
183
|
+
const value = options.get({ get })
|
|
184
|
+
cacheValue(options.key, value, store)
|
|
185
|
+
return value
|
|
186
|
+
}
|
|
187
|
+
if (!(`set` in options)) {
|
|
188
|
+
const readonlySelector: ReadonlySelector<T> = {
|
|
189
|
+
...options,
|
|
190
|
+
subject,
|
|
191
|
+
get: getSelf,
|
|
192
|
+
type: `readonly_selector`,
|
|
193
|
+
...(family && { family }),
|
|
194
|
+
}
|
|
195
|
+
core.readonlySelectors = HAMT.set(
|
|
196
|
+
options.key,
|
|
197
|
+
readonlySelector,
|
|
198
|
+
core.readonlySelectors
|
|
199
|
+
)
|
|
200
|
+
const initialValue = getSelf()
|
|
201
|
+
store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
|
|
202
|
+
return { ...readonlySelector, type: `readonly_selector` }
|
|
203
|
+
}
|
|
204
|
+
const setSelf = (next: T | ((oldValue: T) => T)): void => {
|
|
205
|
+
store.config.logger?.info(` <- "${options.key}" became`, next)
|
|
206
|
+
const oldValue = getSelf()
|
|
207
|
+
const newValue = become(next)(oldValue)
|
|
208
|
+
cacheValue(options.key, newValue, store)
|
|
209
|
+
markDone(options.key, store)
|
|
210
|
+
if (store.transactionStatus.phase === `idle`) {
|
|
211
|
+
subject.next({ newValue, oldValue })
|
|
212
|
+
}
|
|
213
|
+
options.set({ get, set }, newValue)
|
|
214
|
+
}
|
|
215
|
+
const mySelector: Selector<T> = {
|
|
216
|
+
...options,
|
|
217
|
+
subject,
|
|
218
|
+
get: getSelf,
|
|
219
|
+
set: setSelf,
|
|
220
|
+
type: `selector`,
|
|
221
|
+
...(family && { family }),
|
|
222
|
+
}
|
|
223
|
+
core.selectors = HAMT.set(options.key, mySelector, core.selectors)
|
|
224
|
+
const initialValue = getSelf()
|
|
225
|
+
store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
|
|
226
|
+
return { ...mySelector, type: `selector` }
|
|
227
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
|
|
3
|
+
import { become } from "~/packages/anvl/src/function"
|
|
4
|
+
|
|
5
|
+
import type { Atom, Selector, Store } from "."
|
|
6
|
+
import {
|
|
7
|
+
IMPLICIT,
|
|
8
|
+
cacheValue,
|
|
9
|
+
emitUpdate,
|
|
10
|
+
evictCachedValue,
|
|
11
|
+
getState__INTERNAL,
|
|
12
|
+
isAtomDefault,
|
|
13
|
+
isDone,
|
|
14
|
+
markAtomAsNotDefault,
|
|
15
|
+
markDone,
|
|
16
|
+
stowUpdate,
|
|
17
|
+
target,
|
|
18
|
+
} from "."
|
|
19
|
+
|
|
20
|
+
export const evictDownStream = <T>(
|
|
21
|
+
state: Atom<T>,
|
|
22
|
+
store: Store = IMPLICIT.STORE
|
|
23
|
+
): void => {
|
|
24
|
+
const core = target(store)
|
|
25
|
+
const downstream = core.selectorAtoms.getRelations(state.key)
|
|
26
|
+
const downstreamKeys = downstream.map(({ id }) => id)
|
|
27
|
+
store.config.logger?.info(
|
|
28
|
+
` || ${downstreamKeys.length} downstream:`,
|
|
29
|
+
downstreamKeys
|
|
30
|
+
)
|
|
31
|
+
if (core.operation.open) {
|
|
32
|
+
store.config.logger?.info(` ||`, [...core.operation.done], `already done`)
|
|
33
|
+
}
|
|
34
|
+
downstream.forEach(({ id: stateKey }) => {
|
|
35
|
+
if (isDone(stateKey, store)) {
|
|
36
|
+
store.config.logger?.info(` || ${stateKey} already done`)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
const state =
|
|
40
|
+
HAMT.get(stateKey, core.selectors) ??
|
|
41
|
+
HAMT.get(stateKey, core.readonlySelectors)
|
|
42
|
+
if (!state) {
|
|
43
|
+
store.config.logger?.info(
|
|
44
|
+
` || ${stateKey} is an atom, and can't be downstream`
|
|
45
|
+
)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
evictCachedValue(stateKey, store)
|
|
49
|
+
store.config.logger?.info(` xx evicted "${stateKey}"`)
|
|
50
|
+
|
|
51
|
+
markDone(stateKey, store)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const setAtomState = <T>(
|
|
56
|
+
atom: Atom<T>,
|
|
57
|
+
next: T | ((oldValue: T) => T),
|
|
58
|
+
store: Store = IMPLICIT.STORE
|
|
59
|
+
): void => {
|
|
60
|
+
const oldValue = getState__INTERNAL(atom, store)
|
|
61
|
+
const newValue = become(next)(oldValue)
|
|
62
|
+
store.config.logger?.info(`<< setting atom "${atom.key}" to`, newValue)
|
|
63
|
+
cacheValue(atom.key, newValue, store)
|
|
64
|
+
if (isAtomDefault(atom.key)) {
|
|
65
|
+
markAtomAsNotDefault(atom.key, store)
|
|
66
|
+
}
|
|
67
|
+
markDone(atom.key, store)
|
|
68
|
+
store.config.logger?.info(
|
|
69
|
+
` || evicting caches downstream from "${atom.key}"`
|
|
70
|
+
)
|
|
71
|
+
evictDownStream(atom, store)
|
|
72
|
+
const update = { oldValue, newValue }
|
|
73
|
+
if (store.transactionStatus.phase !== `building`) {
|
|
74
|
+
emitUpdate(atom, update, store)
|
|
75
|
+
} else {
|
|
76
|
+
stowUpdate(atom, update, store)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export const setSelectorState = <T>(
|
|
80
|
+
selector: Selector<T>,
|
|
81
|
+
next: T | ((oldValue: T) => T),
|
|
82
|
+
store: Store = IMPLICIT.STORE
|
|
83
|
+
): void => {
|
|
84
|
+
const oldValue = getState__INTERNAL(selector, store)
|
|
85
|
+
const newValue = become(next)(oldValue)
|
|
86
|
+
|
|
87
|
+
store.config.logger?.info(`<< setting selector "${selector.key}" to`, newValue)
|
|
88
|
+
store.config.logger?.info(` || propagating change made to "${selector.key}"`)
|
|
89
|
+
|
|
90
|
+
selector.set(newValue)
|
|
91
|
+
}
|
|
92
|
+
export const setState__INTERNAL = <T>(
|
|
93
|
+
state: Atom<T> | Selector<T>,
|
|
94
|
+
value: T | ((oldValue: T) => T),
|
|
95
|
+
store: Store = IMPLICIT.STORE
|
|
96
|
+
): void => {
|
|
97
|
+
if (`set` in state) {
|
|
98
|
+
setSelectorState(state, value, store)
|
|
99
|
+
} else {
|
|
100
|
+
setAtomState(state, value, store)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Hamt } from "hamt_plus"
|
|
2
|
+
import HAMT from "hamt_plus"
|
|
3
|
+
|
|
4
|
+
import { doNothing } from "~/packages/anvl/src/function"
|
|
5
|
+
import { Join } from "~/packages/anvl/src/join"
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
Atom,
|
|
9
|
+
OperationProgress,
|
|
10
|
+
ReadonlySelector,
|
|
11
|
+
Selector,
|
|
12
|
+
TransactionStatus,
|
|
13
|
+
Logger,
|
|
14
|
+
Timeline,
|
|
15
|
+
TimelineData,
|
|
16
|
+
} from "."
|
|
17
|
+
import type { Transaction, ƒn } from ".."
|
|
18
|
+
|
|
19
|
+
export type StoreCore = Pick<
|
|
20
|
+
Store,
|
|
21
|
+
| `atoms`
|
|
22
|
+
| `atomsThatAreDefault`
|
|
23
|
+
| `operation`
|
|
24
|
+
| `readonlySelectors`
|
|
25
|
+
| `selectorAtoms`
|
|
26
|
+
| `selectorGraph`
|
|
27
|
+
| `selectors`
|
|
28
|
+
| `timelineAtoms`
|
|
29
|
+
| `timelines`
|
|
30
|
+
| `transactions`
|
|
31
|
+
| `valueMap`
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
export interface Store {
|
|
35
|
+
atoms: Hamt<Atom<any>, string>
|
|
36
|
+
atomsThatAreDefault: Set<string>
|
|
37
|
+
readonlySelectors: Hamt<ReadonlySelector<any>, string>
|
|
38
|
+
selectorAtoms: Join
|
|
39
|
+
selectorGraph: Join<{ source: string }>
|
|
40
|
+
selectors: Hamt<Selector<any>, string>
|
|
41
|
+
timelines: Hamt<Timeline, string>
|
|
42
|
+
timelineAtoms: Join
|
|
43
|
+
timelineStore: Hamt<TimelineData, string>
|
|
44
|
+
transactions: Hamt<Transaction<any>, string>
|
|
45
|
+
valueMap: Hamt<any, string>
|
|
46
|
+
|
|
47
|
+
operation: OperationProgress
|
|
48
|
+
transactionStatus: TransactionStatus<ƒn>
|
|
49
|
+
config: {
|
|
50
|
+
name: string
|
|
51
|
+
logger: Logger | null
|
|
52
|
+
logger__INTERNAL: Logger
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const createStore = (name: string): Store =>
|
|
57
|
+
({
|
|
58
|
+
atoms: HAMT.make<Atom<any>, string>(),
|
|
59
|
+
atomsThatAreDefault: new Set(),
|
|
60
|
+
readonlySelectors: HAMT.make<ReadonlySelector<any>, string>(),
|
|
61
|
+
selectorAtoms: new Join({ relationType: `n:n` }),
|
|
62
|
+
selectorGraph: new Join({ relationType: `n:n` }),
|
|
63
|
+
selectors: HAMT.make<Selector<any>, string>(),
|
|
64
|
+
timelines: HAMT.make<Timeline, string>(),
|
|
65
|
+
timelineAtoms: new Join({ relationType: `1:n` }),
|
|
66
|
+
timelineStore: HAMT.make<TimelineData, string>(),
|
|
67
|
+
transactions: HAMT.make<Transaction<any>, string>(),
|
|
68
|
+
valueMap: HAMT.make<any, string>(),
|
|
69
|
+
|
|
70
|
+
operation: {
|
|
71
|
+
open: false,
|
|
72
|
+
},
|
|
73
|
+
transactionStatus: {
|
|
74
|
+
phase: `idle`,
|
|
75
|
+
},
|
|
76
|
+
config: {
|
|
77
|
+
name,
|
|
78
|
+
logger: {
|
|
79
|
+
...console,
|
|
80
|
+
info: doNothing,
|
|
81
|
+
},
|
|
82
|
+
logger__INTERNAL: console,
|
|
83
|
+
},
|
|
84
|
+
} satisfies Store)
|
|
85
|
+
|
|
86
|
+
export const IMPLICIT = {
|
|
87
|
+
STORE_INTERNAL: undefined as Store | undefined,
|
|
88
|
+
get STORE(): Store {
|
|
89
|
+
return this.STORE_INTERNAL ?? (this.STORE_INTERNAL = createStore(`DEFAULT`))
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const clearStore = (store: Store = IMPLICIT.STORE): void => {
|
|
94
|
+
const { config } = store
|
|
95
|
+
Object.assign(store, createStore(config.name))
|
|
96
|
+
store.config = config
|
|
97
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getState__INTERNAL,
|
|
3
|
+
withdraw,
|
|
4
|
+
recallState,
|
|
5
|
+
traceAllSelectorAtoms,
|
|
6
|
+
} from "."
|
|
7
|
+
import type { Atom, ReadonlySelector, Selector, Store } from "."
|
|
8
|
+
import type { StateUpdate } from ".."
|
|
9
|
+
|
|
10
|
+
export const prepareUpdate = <T>(
|
|
11
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
12
|
+
store: Store
|
|
13
|
+
): StateUpdate<T> => {
|
|
14
|
+
const oldValue = recallState(state, store)
|
|
15
|
+
const newValue = getState__INTERNAL(state, store)
|
|
16
|
+
return { newValue, oldValue }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const stowUpdate = <T>(
|
|
20
|
+
state: Atom<T>,
|
|
21
|
+
update: StateUpdate<T>,
|
|
22
|
+
store: Store
|
|
23
|
+
): void => {
|
|
24
|
+
const { key } = state
|
|
25
|
+
const { logger } = store.config
|
|
26
|
+
if (store.transactionStatus.phase !== `building`) {
|
|
27
|
+
store.config.logger?.warn(
|
|
28
|
+
`stowUpdate called outside of a transaction. This is probably a bug.`
|
|
29
|
+
)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
store.transactionStatus.atomUpdates.push({ key, ...update })
|
|
33
|
+
logger?.info(`📝 ${key} stowed (`, update.oldValue, `->`, update.newValue, `)`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const emitUpdate = <T>(
|
|
37
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
38
|
+
update: StateUpdate<T>,
|
|
39
|
+
store: Store
|
|
40
|
+
): void => {
|
|
41
|
+
const { key } = state
|
|
42
|
+
const { logger } = store.config
|
|
43
|
+
logger?.info(
|
|
44
|
+
`📢 ${state.type} "${key}" went (`,
|
|
45
|
+
update.oldValue,
|
|
46
|
+
`->`,
|
|
47
|
+
update.newValue,
|
|
48
|
+
`)`
|
|
49
|
+
)
|
|
50
|
+
state.subject.next(update)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const subscribeToRootAtoms = <T>(
|
|
54
|
+
state: ReadonlySelector<T> | Selector<T>,
|
|
55
|
+
store: Store
|
|
56
|
+
): { unsubscribe: () => void }[] | null => {
|
|
57
|
+
const dependencySubscriptions =
|
|
58
|
+
`default` in state
|
|
59
|
+
? null
|
|
60
|
+
: traceAllSelectorAtoms(state.key, store).map((atomToken) => {
|
|
61
|
+
const atom = withdraw(atomToken, store)
|
|
62
|
+
return atom.subject.subscribe((atomChange) => {
|
|
63
|
+
store.config.logger?.info(
|
|
64
|
+
`📢 selector "${state.key}" saw root "${atomToken.key}" go (`,
|
|
65
|
+
atomChange.oldValue,
|
|
66
|
+
`->`,
|
|
67
|
+
atomChange.newValue,
|
|
68
|
+
`)`
|
|
69
|
+
)
|
|
70
|
+
const oldValue = recallState(state, store)
|
|
71
|
+
const newValue = getState__INTERNAL(state, store)
|
|
72
|
+
store.config.logger?.info(` <- ${state.key} became`, newValue)
|
|
73
|
+
state.subject.next({ newValue, oldValue })
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
return dependencySubscriptions
|
|
77
|
+
}
|