atom.io 0.6.9 โ 0.7.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 +21 -2
- package/dist/index.d.mts +34 -421
- package/dist/index.d.ts +34 -421
- package/dist/index.js +248 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -4
- package/dist/index.mjs.map +1 -1
- package/internal/dist/index.d.mts +342 -0
- package/internal/dist/index.d.ts +342 -0
- package/internal/dist/index.js +1873 -0
- package/internal/dist/index.js.map +1 -0
- package/internal/dist/index.mjs +1798 -0
- package/internal/dist/index.mjs.map +1 -0
- package/internal/package.json +15 -0
- package/internal/src/atom/create-atom.ts +75 -0
- package/internal/src/atom/delete-atom.ts +10 -0
- package/internal/src/atom/index.ts +3 -0
- package/internal/src/atom/is-default.ts +37 -0
- package/internal/src/caching.ts +21 -0
- package/internal/src/families/create-atom-family.ts +59 -0
- package/internal/src/families/create-readonly-selector-family.ts +45 -0
- package/internal/src/families/create-selector-family.ts +67 -0
- package/internal/src/families/index.ts +3 -0
- package/internal/src/get-state-internal.ts +23 -0
- package/internal/src/index.ts +13 -0
- package/internal/src/mutable/create-mutable-atom-family.ts +25 -0
- package/internal/src/mutable/create-mutable-atom.ts +49 -0
- package/internal/src/mutable/get-json-token.ts +22 -0
- package/internal/src/mutable/get-update-token.ts +20 -0
- package/internal/src/mutable/index.ts +17 -0
- package/internal/src/mutable/is-atom-token-mutable.ts +7 -0
- package/internal/src/mutable/tracker-family.ts +61 -0
- package/internal/src/mutable/tracker.ts +164 -0
- package/internal/src/mutable/transceiver.ts +110 -0
- package/internal/src/operation.ts +68 -0
- package/internal/src/selector/create-read-write-selector.ts +65 -0
- package/internal/src/selector/create-readonly-selector.ts +49 -0
- package/internal/src/selector/create-selector.ts +65 -0
- package/internal/src/selector/index.ts +5 -0
- package/internal/src/selector/lookup-selector-sources.ts +20 -0
- package/internal/src/selector/register-selector.ts +61 -0
- package/internal/src/selector/trace-selector-atoms.ts +45 -0
- package/internal/src/selector/update-selector-atoms.ts +34 -0
- package/internal/src/set-state/become.ts +10 -0
- package/internal/src/set-state/copy-mutable-if-needed.ts +23 -0
- package/internal/src/set-state/copy-mutable-in-transaction.ts +59 -0
- package/internal/src/set-state/copy-mutable-into-new-store.ts +34 -0
- package/internal/src/set-state/emit-update.ts +23 -0
- package/internal/src/set-state/evict-downstream.ts +39 -0
- package/internal/src/set-state/index.ts +2 -0
- package/internal/src/set-state/set-atom-state.ts +38 -0
- package/internal/src/set-state/set-selector-state.ts +19 -0
- package/internal/src/set-state/set-state-internal.ts +18 -0
- package/internal/src/set-state/stow-update.ts +42 -0
- package/internal/src/store/deposit.ts +43 -0
- package/internal/src/store/index.ts +5 -0
- package/internal/src/store/lookup.ts +26 -0
- package/internal/src/store/store.ts +154 -0
- package/internal/src/store/withdraw-new-family-member.ts +53 -0
- package/internal/src/store/withdraw.ts +113 -0
- package/internal/src/subject.ts +21 -0
- package/internal/src/subscribe/index.ts +1 -0
- package/internal/src/subscribe/recall-state.ts +19 -0
- package/internal/src/subscribe/subscribe-to-root-atoms.ts +47 -0
- package/internal/src/timeline/add-atom-to-timeline.ts +189 -0
- package/internal/src/timeline/index.ts +3 -0
- package/internal/src/timeline/time-travel-internal.ts +91 -0
- package/internal/src/timeline/timeline-internal.ts +115 -0
- package/internal/src/transaction/abort-transaction.ts +12 -0
- package/internal/src/transaction/apply-transaction.ts +64 -0
- package/internal/src/transaction/build-transaction.ts +39 -0
- package/internal/src/transaction/index.ts +26 -0
- package/internal/src/transaction/redo-transaction.ts +22 -0
- package/internal/src/transaction/transaction-internal.ts +64 -0
- package/internal/src/transaction/undo-transaction.ts +22 -0
- package/introspection/dist/index.d.mts +3 -197
- package/introspection/dist/index.d.ts +3 -197
- package/introspection/dist/index.js +329 -4
- package/introspection/dist/index.js.map +1 -1
- package/introspection/dist/index.mjs +310 -4
- package/introspection/dist/index.mjs.map +1 -1
- package/introspection/src/attach-atom-index.ts +84 -0
- package/introspection/src/attach-introspection-states.ts +38 -0
- package/introspection/src/attach-selector-index.ts +90 -0
- package/introspection/src/attach-timeline-family.ts +59 -0
- package/introspection/src/attach-timeline-index.ts +38 -0
- package/introspection/src/attach-transaction-index.ts +40 -0
- package/introspection/src/attach-transaction-logs.ts +43 -0
- package/introspection/src/index.ts +20 -0
- package/json/dist/index.d.mts +10 -2
- package/json/dist/index.d.ts +10 -2
- package/json/dist/index.js +83 -26
- package/json/dist/index.js.map +1 -1
- package/json/dist/index.mjs +74 -3
- package/json/dist/index.mjs.map +1 -1
- package/json/src/index.ts +5 -0
- package/json/src/select-json-family.ts +35 -0
- package/json/src/select-json.ts +22 -0
- package/package.json +103 -63
- package/react/dist/index.d.mts +9 -17
- package/react/dist/index.d.ts +9 -17
- package/react/dist/index.js +44 -27
- package/react/dist/index.js.map +1 -1
- package/react/dist/index.mjs +24 -4
- package/react/dist/index.mjs.map +1 -1
- package/react/src/index.ts +2 -0
- package/react/src/store-context.tsx +12 -0
- package/react/src/store-hooks.ts +36 -0
- package/react-devtools/dist/index.css +50 -1
- package/react-devtools/dist/index.css.map +1 -1
- package/react-devtools/dist/index.d.mts +104 -71
- package/react-devtools/dist/index.d.ts +104 -71
- package/react-devtools/dist/index.js +2806 -44
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/dist/index.mjs +2775 -10
- package/react-devtools/dist/index.mjs.map +1 -1
- package/react-devtools/src/AtomIODevtools.tsx +109 -0
- package/react-devtools/src/Button.tsx +23 -0
- package/react-devtools/src/StateEditor.tsx +75 -0
- package/react-devtools/src/StateIndex.tsx +159 -0
- package/react-devtools/src/TimelineIndex.tsx +88 -0
- package/react-devtools/src/TransactionIndex.tsx +70 -0
- package/react-devtools/src/Updates.tsx +150 -0
- package/react-devtools/src/devtools.scss +310 -0
- package/react-devtools/src/index.ts +72 -0
- package/realtime-react/dist/index.d.mts +8 -22
- package/realtime-react/dist/index.d.ts +8 -22
- package/realtime-react/dist/index.js +87 -32
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/dist/index.mjs +62 -6
- package/realtime-react/dist/index.mjs.map +1 -1
- package/realtime-react/src/index.ts +7 -0
- package/realtime-react/src/realtime-context.tsx +29 -0
- package/realtime-react/src/use-pull-family-member.ts +15 -0
- package/realtime-react/src/use-pull-mutable-family-member.ts +20 -0
- package/realtime-react/src/use-pull-mutable.ts +17 -0
- package/realtime-react/src/use-pull.ts +15 -0
- package/realtime-react/src/use-push.ts +19 -0
- package/realtime-react/src/use-server-action.ts +18 -0
- package/realtime-testing/dist/index.d.mts +49 -0
- package/realtime-testing/dist/index.d.ts +49 -0
- package/realtime-testing/dist/index.js +147 -0
- package/realtime-testing/dist/index.js.map +1 -0
- package/realtime-testing/dist/index.mjs +116 -0
- package/realtime-testing/dist/index.mjs.map +1 -0
- package/realtime-testing/src/index.ts +1 -0
- package/realtime-testing/src/setup-realtime-test.tsx +161 -0
- package/src/atom.ts +64 -9
- package/src/index.ts +36 -32
- package/src/logger.ts +3 -3
- package/src/selector.ts +3 -3
- package/src/silo.ts +29 -20
- package/src/subscribe.ts +3 -3
- package/src/timeline.ts +2 -2
- package/transceivers/set-rtx/dist/index.d.mts +39 -0
- package/transceivers/set-rtx/dist/index.d.ts +39 -0
- package/transceivers/set-rtx/dist/index.js +213 -0
- package/transceivers/set-rtx/dist/index.js.map +1 -0
- package/transceivers/set-rtx/dist/index.mjs +211 -0
- package/transceivers/set-rtx/dist/index.mjs.map +1 -0
- package/{realtime โ transceivers/set-rtx}/package.json +1 -1
- package/transceivers/set-rtx/src/index.ts +1 -0
- package/transceivers/set-rtx/src/set-rtx.ts +242 -0
- package/realtime/dist/index.d.mts +0 -23
- package/realtime/dist/index.d.ts +0 -23
- package/realtime/dist/index.js +0 -32
- package/realtime/dist/index.js.map +0 -1
- package/realtime/dist/index.mjs +0 -7
- package/realtime/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FamilyMetadata,
|
|
3
|
+
ReadonlySelectorFamily,
|
|
4
|
+
ReadonlySelectorFamilyOptions,
|
|
5
|
+
SelectorFamily,
|
|
6
|
+
SelectorFamilyOptions,
|
|
7
|
+
SelectorToken,
|
|
8
|
+
} from "atom.io"
|
|
9
|
+
import type { Json } from "atom.io/json"
|
|
10
|
+
import { stringifyJson } from "atom.io/json"
|
|
11
|
+
|
|
12
|
+
import { createSelector } from "../selector"
|
|
13
|
+
import type { Store } from "../store"
|
|
14
|
+
import { IMPLICIT, deposit } from "../store"
|
|
15
|
+
import { Subject } from "../subject"
|
|
16
|
+
import { target } from "../transaction"
|
|
17
|
+
import { createReadonlySelectorFamily } from "./create-readonly-selector-family"
|
|
18
|
+
|
|
19
|
+
export function createSelectorFamily<T, K extends Json.Serializable>(
|
|
20
|
+
options: SelectorFamilyOptions<T, K>,
|
|
21
|
+
store?: Store,
|
|
22
|
+
): SelectorFamily<T, K>
|
|
23
|
+
export function createSelectorFamily<T, K extends Json.Serializable>(
|
|
24
|
+
options: ReadonlySelectorFamilyOptions<T, K>,
|
|
25
|
+
store?: Store,
|
|
26
|
+
): ReadonlySelectorFamily<T, K>
|
|
27
|
+
export function createSelectorFamily<T, K extends Json.Serializable>(
|
|
28
|
+
options: ReadonlySelectorFamilyOptions<T, K> | SelectorFamilyOptions<T, K>,
|
|
29
|
+
store: Store = IMPLICIT.STORE,
|
|
30
|
+
): ReadonlySelectorFamily<T, K> | SelectorFamily<T, K> {
|
|
31
|
+
const isReadonly = !(`set` in options)
|
|
32
|
+
|
|
33
|
+
if (isReadonly) {
|
|
34
|
+
return createReadonlySelectorFamily(options, store)
|
|
35
|
+
}
|
|
36
|
+
const core = target(store)
|
|
37
|
+
const subject = new Subject<SelectorToken<T>>()
|
|
38
|
+
|
|
39
|
+
const selectorFamily = Object.assign(
|
|
40
|
+
(key: K): SelectorToken<T> => {
|
|
41
|
+
const subKey = stringifyJson(key)
|
|
42
|
+
const family: FamilyMetadata = { key: options.key, subKey }
|
|
43
|
+
const fullKey = `${options.key}(${subKey})`
|
|
44
|
+
const existing = core.selectors.get(fullKey)
|
|
45
|
+
if (existing) {
|
|
46
|
+
return deposit(existing)
|
|
47
|
+
}
|
|
48
|
+
const token = createSelector<T>(
|
|
49
|
+
{
|
|
50
|
+
key: fullKey,
|
|
51
|
+
get: options.get(key),
|
|
52
|
+
set: options.set(key),
|
|
53
|
+
},
|
|
54
|
+
family,
|
|
55
|
+
store,
|
|
56
|
+
)
|
|
57
|
+
subject.next(token)
|
|
58
|
+
return token
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: options.key,
|
|
62
|
+
type: `selector_family`,
|
|
63
|
+
} as const,
|
|
64
|
+
) as SelectorFamily<T, K>
|
|
65
|
+
core.families.set(options.key, selectorFamily)
|
|
66
|
+
return selectorFamily
|
|
67
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Atom } from "./atom"
|
|
2
|
+
import { isValueCached, readCachedValue } from "./caching"
|
|
3
|
+
import type { ReadonlySelector, Selector } from "./selector"
|
|
4
|
+
import type { Store } from "./store"
|
|
5
|
+
import { IMPLICIT } from "./store"
|
|
6
|
+
|
|
7
|
+
export const getState__INTERNAL = <T>(
|
|
8
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
9
|
+
store: Store = IMPLICIT.STORE,
|
|
10
|
+
): T => {
|
|
11
|
+
if (isValueCached(state.key, store)) {
|
|
12
|
+
store.config.logger?.info(`>> read "${state.key}"`)
|
|
13
|
+
return readCachedValue(state.key, store)
|
|
14
|
+
}
|
|
15
|
+
if (state.type !== `atom`) {
|
|
16
|
+
store.config.logger?.info(`-> calc "${state.key}"`)
|
|
17
|
+
return state.get()
|
|
18
|
+
}
|
|
19
|
+
store.config.logger?.error(
|
|
20
|
+
`Attempted to get atom "${state.key}", which was never initialized in store "${store.config.name}".`,
|
|
21
|
+
)
|
|
22
|
+
return state.default
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./atom"
|
|
2
|
+
export * from "./caching"
|
|
3
|
+
export * from "./families"
|
|
4
|
+
export * from "./get-state-internal"
|
|
5
|
+
export * from "./operation"
|
|
6
|
+
export * from "./mutable"
|
|
7
|
+
export * from "./selector"
|
|
8
|
+
export * from "./set-state"
|
|
9
|
+
export * from "./store"
|
|
10
|
+
export * from "./subject"
|
|
11
|
+
export * from "./subscribe"
|
|
12
|
+
export * from "./timeline"
|
|
13
|
+
export * from "./transaction"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
import { selectJsonFamily } from "atom.io/json"
|
|
4
|
+
|
|
5
|
+
import type { Store } from ".."
|
|
6
|
+
import { IMPLICIT, createAtomFamily } from ".."
|
|
7
|
+
import { FamilyTracker } from "./tracker-family"
|
|
8
|
+
import type { Transceiver } from "./transceiver"
|
|
9
|
+
|
|
10
|
+
export function createMutableAtomFamily<
|
|
11
|
+
Core extends Transceiver<any>,
|
|
12
|
+
SerializableCore extends Json.Serializable,
|
|
13
|
+
Key extends string,
|
|
14
|
+
>(
|
|
15
|
+
options: AtomIO.MutableAtomFamilyOptions<Core, SerializableCore, Key>,
|
|
16
|
+
store: Store = IMPLICIT.STORE,
|
|
17
|
+
): AtomIO.MutableAtomFamily<Core, SerializableCore, Key> {
|
|
18
|
+
const coreFamily = Object.assign(
|
|
19
|
+
createAtomFamily<Core, Key>(options, store),
|
|
20
|
+
options,
|
|
21
|
+
) as AtomIO.MutableAtomFamily<Core, SerializableCore, Key>
|
|
22
|
+
selectJsonFamily(coreFamily, options)
|
|
23
|
+
new FamilyTracker(coreFamily, store)
|
|
24
|
+
return coreFamily
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as AtomIO from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
import { selectJson } from "atom.io/json"
|
|
4
|
+
|
|
5
|
+
import { createAtom } from "../atom"
|
|
6
|
+
import type { Store } from "../store"
|
|
7
|
+
import { IMPLICIT } from "../store"
|
|
8
|
+
import { target } from "../transaction"
|
|
9
|
+
import { Tracker } from "./tracker"
|
|
10
|
+
import type { Transceiver } from "./transceiver"
|
|
11
|
+
|
|
12
|
+
export function createMutableAtom<
|
|
13
|
+
Core extends Transceiver<any>,
|
|
14
|
+
SerializableCore extends Json.Serializable,
|
|
15
|
+
>(
|
|
16
|
+
options: AtomIO.MutableAtomOptions<Core, SerializableCore>,
|
|
17
|
+
store: Store = IMPLICIT.STORE,
|
|
18
|
+
): AtomIO.MutableAtomToken<Core, SerializableCore> {
|
|
19
|
+
store.config.logger?.info(
|
|
20
|
+
`๐ง creating mutable atom "${options.key}" in store "${store.config.name}"`,
|
|
21
|
+
)
|
|
22
|
+
const coreState = createAtom<Core>(options, undefined, store)
|
|
23
|
+
new Tracker(coreState, store)
|
|
24
|
+
const jsonState = selectJson(coreState, options, store)
|
|
25
|
+
AtomIO.subscribe(
|
|
26
|
+
jsonState,
|
|
27
|
+
() => {
|
|
28
|
+
store.config.logger?.info(
|
|
29
|
+
`๐ tracker-initializer:${store?.config.name}:${
|
|
30
|
+
store.transactionStatus.phase === `idle`
|
|
31
|
+
? `main`
|
|
32
|
+
: store.transactionStatus.key
|
|
33
|
+
}`,
|
|
34
|
+
`Initializing tracker for ${coreState.key}`,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const trackerHasBeenInitialized = target(store).trackers.has(coreState.key)
|
|
38
|
+
if (!trackerHasBeenInitialized) {
|
|
39
|
+
new Tracker(coreState, store)
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
`tracker-initializer:${store?.config.name}:${
|
|
43
|
+
store.transactionStatus.phase === `idle`
|
|
44
|
+
? `main`
|
|
45
|
+
: store.transactionStatus.key
|
|
46
|
+
}`,
|
|
47
|
+
)
|
|
48
|
+
return coreState as AtomIO.MutableAtomToken<Core, SerializableCore>
|
|
49
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MutableAtomToken, SelectorToken } from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
import type { Transceiver } from "./transceiver"
|
|
4
|
+
|
|
5
|
+
export const getJsonToken = <
|
|
6
|
+
Core extends Transceiver<Json.Serializable>,
|
|
7
|
+
SerializableCore extends Json.Serializable,
|
|
8
|
+
>(
|
|
9
|
+
mutableAtomToken: MutableAtomToken<Core, SerializableCore>,
|
|
10
|
+
): SelectorToken<SerializableCore> => {
|
|
11
|
+
const key = mutableAtomToken.family
|
|
12
|
+
? `${mutableAtomToken.family.key}:JSON(${mutableAtomToken.family.subKey})`
|
|
13
|
+
: `${mutableAtomToken.key}:JSON`
|
|
14
|
+
const jsonToken: SelectorToken<SerializableCore> = { type: `selector`, key }
|
|
15
|
+
if (mutableAtomToken.family) {
|
|
16
|
+
jsonToken.family = {
|
|
17
|
+
key: `${mutableAtomToken.family.key}:JSON`,
|
|
18
|
+
subKey: mutableAtomToken.family.subKey,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return jsonToken
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AtomToken, MutableAtomToken } from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
import type { Signal, Transceiver } from "./transceiver"
|
|
4
|
+
|
|
5
|
+
export const getUpdateToken = <
|
|
6
|
+
Core extends Transceiver<Json.Serializable>,
|
|
7
|
+
SerializableCore extends Json.Serializable,
|
|
8
|
+
>(
|
|
9
|
+
mutableAtomToken: MutableAtomToken<Core, SerializableCore>,
|
|
10
|
+
): AtomToken<Signal<Core>> => {
|
|
11
|
+
const key = `*${mutableAtomToken.key}`
|
|
12
|
+
const updateToken: AtomToken<Signal<Core>> = { type: `atom`, key }
|
|
13
|
+
if (mutableAtomToken.family) {
|
|
14
|
+
updateToken.family = {
|
|
15
|
+
key: `*${mutableAtomToken.family.key}`,
|
|
16
|
+
subKey: mutableAtomToken.family.subKey,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return updateToken
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Atom } from "../atom"
|
|
2
|
+
|
|
3
|
+
export * from "./create-mutable-atom"
|
|
4
|
+
export * from "./create-mutable-atom-family"
|
|
5
|
+
export * from "./get-json-token"
|
|
6
|
+
export * from "./get-update-token"
|
|
7
|
+
export * from "./is-atom-token-mutable"
|
|
8
|
+
export * from "./tracker"
|
|
9
|
+
export * from "./tracker-family"
|
|
10
|
+
export * from "./transceiver"
|
|
11
|
+
|
|
12
|
+
export interface MutableAtom<T> extends Atom<T> {
|
|
13
|
+
mutable: true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const isAtomMutable = <T>(atom: Atom<T>): atom is MutableAtom<T> =>
|
|
17
|
+
`isMutable` in atom
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
import { parseJson } from "atom.io/json"
|
|
4
|
+
|
|
5
|
+
import { createAtomFamily } from "../families"
|
|
6
|
+
import type { Store } from "../store"
|
|
7
|
+
import { IMPLICIT } from "../store"
|
|
8
|
+
import { Tracker } from "./tracker"
|
|
9
|
+
import type { Transceiver } from "./transceiver"
|
|
10
|
+
|
|
11
|
+
export class FamilyTracker<
|
|
12
|
+
Core extends Transceiver<any>,
|
|
13
|
+
FamilyMemberKey extends Json.Serializable,
|
|
14
|
+
> {
|
|
15
|
+
private readonly Update: Core extends Transceiver<infer Signal>
|
|
16
|
+
? Signal
|
|
17
|
+
: never
|
|
18
|
+
|
|
19
|
+
public readonly findLatestUpdateState: AtomIO.AtomFamily<
|
|
20
|
+
typeof this.Update | null,
|
|
21
|
+
FamilyMemberKey
|
|
22
|
+
>
|
|
23
|
+
public readonly findMutableState: AtomIO.AtomFamily<Core, FamilyMemberKey>
|
|
24
|
+
|
|
25
|
+
public constructor(
|
|
26
|
+
findMutableState: AtomIO.AtomFamily<Core, FamilyMemberKey>,
|
|
27
|
+
store: Store = IMPLICIT.STORE,
|
|
28
|
+
) {
|
|
29
|
+
this.findLatestUpdateState = createAtomFamily<
|
|
30
|
+
typeof this.Update | null,
|
|
31
|
+
FamilyMemberKey
|
|
32
|
+
>(
|
|
33
|
+
{
|
|
34
|
+
key: `*${findMutableState.key}`,
|
|
35
|
+
default: null,
|
|
36
|
+
},
|
|
37
|
+
store,
|
|
38
|
+
)
|
|
39
|
+
this.findMutableState = findMutableState
|
|
40
|
+
this.findMutableState.subject.subscribe(
|
|
41
|
+
`store=${store.config.name}::tracker-atom-family`,
|
|
42
|
+
(atomToken) => {
|
|
43
|
+
if (atomToken.family) {
|
|
44
|
+
const key = parseJson(atomToken.family.subKey) as FamilyMemberKey
|
|
45
|
+
this.findLatestUpdateState(key)
|
|
46
|
+
new Tracker<Core>(atomToken, store)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
this.findLatestUpdateState.subject.subscribe(
|
|
51
|
+
`store=${store.config.name}::tracker-atom-family`,
|
|
52
|
+
(atomToken) => {
|
|
53
|
+
if (atomToken.family) {
|
|
54
|
+
const key = parseJson(atomToken.family.subKey) as FamilyMemberKey
|
|
55
|
+
const mutableAtomToken = this.findMutableState(key)
|
|
56
|
+
new Tracker<Core>(mutableAtomToken, store)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as AtomIO from "atom.io"
|
|
2
|
+
import type { Json } from "atom.io/json"
|
|
3
|
+
|
|
4
|
+
import type { Store } from ".."
|
|
5
|
+
import { IMPLICIT } from ".."
|
|
6
|
+
import { createAtom, deleteAtom } from "../atom"
|
|
7
|
+
import { target } from "../transaction"
|
|
8
|
+
import type { Transceiver } from "./transceiver"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal Give the tracker a transceiver state and a store, and it will
|
|
12
|
+
* subscribe to the transceiver's inner value. When the inner value changes,
|
|
13
|
+
* the tracker will update its own state to reflect the change.
|
|
14
|
+
*/
|
|
15
|
+
export class Tracker<Mutable extends Transceiver<any>> {
|
|
16
|
+
private Update: Mutable extends Transceiver<infer Signal> ? Signal : never
|
|
17
|
+
|
|
18
|
+
private initializeState(
|
|
19
|
+
mutableState: AtomIO.MutableAtomToken<Mutable, Json.Serializable>,
|
|
20
|
+
store: Store = IMPLICIT.STORE,
|
|
21
|
+
): AtomIO.AtomToken<typeof this.Update | null> {
|
|
22
|
+
const latestUpdateStateKey = `*${mutableState.key}`
|
|
23
|
+
deleteAtom(latestUpdateStateKey, target(store))
|
|
24
|
+
const familyMetaData: AtomIO.FamilyMetadata | undefined = mutableState.family
|
|
25
|
+
? {
|
|
26
|
+
key: `*${mutableState.family.key}`,
|
|
27
|
+
subKey: mutableState.family.subKey,
|
|
28
|
+
}
|
|
29
|
+
: undefined
|
|
30
|
+
const latestUpdateState = createAtom<
|
|
31
|
+
(Mutable extends Transceiver<infer Signal> ? Signal : never) | null
|
|
32
|
+
>(
|
|
33
|
+
{
|
|
34
|
+
key: latestUpdateStateKey,
|
|
35
|
+
default: null,
|
|
36
|
+
},
|
|
37
|
+
familyMetaData,
|
|
38
|
+
store,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return latestUpdateState
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private unsubscribeFromInnerValue: (() => void) | null = null
|
|
45
|
+
private observeCore(
|
|
46
|
+
mutableState: AtomIO.MutableAtomToken<Mutable, Json.Serializable>,
|
|
47
|
+
latestUpdateState: AtomIO.AtomToken<typeof this.Update | null>,
|
|
48
|
+
store: Store = IMPLICIT.STORE,
|
|
49
|
+
): void {
|
|
50
|
+
const originalInnerValue = AtomIO.getState(mutableState, store)
|
|
51
|
+
this.unsubscribeFromInnerValue = originalInnerValue.subscribe(
|
|
52
|
+
`tracker:${store.config.name}:${
|
|
53
|
+
store.transactionStatus.phase === `idle`
|
|
54
|
+
? `main`
|
|
55
|
+
: store.transactionStatus.key
|
|
56
|
+
}`,
|
|
57
|
+
(update) => {
|
|
58
|
+
const unsubscribe = store.subject.operationStatus.subscribe(
|
|
59
|
+
mutableState.key,
|
|
60
|
+
() => {
|
|
61
|
+
unsubscribe()
|
|
62
|
+
AtomIO.setState(latestUpdateState, update, store)
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
AtomIO.subscribe(
|
|
68
|
+
mutableState,
|
|
69
|
+
(update) => {
|
|
70
|
+
if (update.newValue !== update.oldValue) {
|
|
71
|
+
this.unsubscribeFromInnerValue?.()
|
|
72
|
+
this.unsubscribeFromInnerValue = update.newValue.subscribe(
|
|
73
|
+
`tracker:${store.config.name}:${
|
|
74
|
+
store.transactionStatus.phase === `idle`
|
|
75
|
+
? `main`
|
|
76
|
+
: store.transactionStatus.key
|
|
77
|
+
}`,
|
|
78
|
+
(update) => {
|
|
79
|
+
const unsubscribe = store.subject.operationStatus.subscribe(
|
|
80
|
+
mutableState.key,
|
|
81
|
+
() => {
|
|
82
|
+
unsubscribe()
|
|
83
|
+
AtomIO.setState(latestUpdateState, update, store)
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
`${store.config.name}: tracker observing inner value`,
|
|
91
|
+
store,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private updateCore<Core extends Transceiver<any>>(
|
|
96
|
+
mutableState: AtomIO.MutableAtomToken<Core, Json.Serializable>,
|
|
97
|
+
latestUpdateState: AtomIO.AtomToken<typeof this.Update | null>,
|
|
98
|
+
store: Store = IMPLICIT.STORE,
|
|
99
|
+
): void {
|
|
100
|
+
AtomIO.subscribe(
|
|
101
|
+
latestUpdateState,
|
|
102
|
+
({ newValue, oldValue }) => {
|
|
103
|
+
const timelineId = store.timelineAtoms.getRelatedKey(
|
|
104
|
+
latestUpdateState.key,
|
|
105
|
+
)
|
|
106
|
+
if (timelineId) {
|
|
107
|
+
const timelineData = store.timelines.get(timelineId)
|
|
108
|
+
if (timelineData?.timeTraveling) {
|
|
109
|
+
const unsubscribe = AtomIO.subscribeToTimeline(
|
|
110
|
+
{ key: timelineId, type: `timeline` },
|
|
111
|
+
(update) => {
|
|
112
|
+
unsubscribe()
|
|
113
|
+
AtomIO.setState(
|
|
114
|
+
mutableState,
|
|
115
|
+
(transceiver) => {
|
|
116
|
+
if (update === `redo` && newValue) {
|
|
117
|
+
transceiver.do(newValue)
|
|
118
|
+
} else if (update === `undo` && oldValue) {
|
|
119
|
+
transceiver.undo(oldValue)
|
|
120
|
+
}
|
|
121
|
+
return transceiver
|
|
122
|
+
},
|
|
123
|
+
store,
|
|
124
|
+
)
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const unsubscribe = store.subject.operationStatus.subscribe(
|
|
132
|
+
latestUpdateState.key,
|
|
133
|
+
() => {
|
|
134
|
+
unsubscribe()
|
|
135
|
+
if (newValue) {
|
|
136
|
+
AtomIO.setState(
|
|
137
|
+
mutableState,
|
|
138
|
+
(transceiver) => (transceiver.do(newValue), transceiver),
|
|
139
|
+
store,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
`${store.config.name}: tracker observing latest update`,
|
|
146
|
+
store,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public mutableState: AtomIO.MutableAtomToken<Mutable, Json.Serializable>
|
|
151
|
+
public latestUpdateState: AtomIO.AtomToken<typeof this.Update | null>
|
|
152
|
+
|
|
153
|
+
public constructor(
|
|
154
|
+
mutableState: AtomIO.MutableAtomToken<Mutable, Json.Serializable>,
|
|
155
|
+
store: Store = IMPLICIT.STORE,
|
|
156
|
+
) {
|
|
157
|
+
this.mutableState = mutableState
|
|
158
|
+
this.latestUpdateState = this.initializeState(mutableState, store)
|
|
159
|
+
this.observeCore(mutableState, this.latestUpdateState, store)
|
|
160
|
+
this.updateCore(mutableState, this.latestUpdateState, store)
|
|
161
|
+
const core = target(store)
|
|
162
|
+
core.trackers.set(mutableState.key, this)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Json } from "atom.io/json"
|
|
2
|
+
|
|
3
|
+
export interface Transceiver<Signal extends Json.Serializable> {
|
|
4
|
+
do: (update: Signal) => void
|
|
5
|
+
undo: (update: Signal) => void
|
|
6
|
+
subscribe: (key: string, fn: (update: Signal) => void) => () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isTransceiver(
|
|
10
|
+
value: unknown,
|
|
11
|
+
): value is Transceiver<Json.Serializable> {
|
|
12
|
+
return (
|
|
13
|
+
typeof value === `object` &&
|
|
14
|
+
value !== null &&
|
|
15
|
+
`do` in value &&
|
|
16
|
+
`undo` in value &&
|
|
17
|
+
`subscribe` in value
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TransceiverMode = `playback` | `record` | `transaction`
|
|
22
|
+
|
|
23
|
+
export type Signal<TVR extends Transceiver<any>> = TVR extends Transceiver<
|
|
24
|
+
infer Signal
|
|
25
|
+
>
|
|
26
|
+
? Signal
|
|
27
|
+
: never
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
A transceiver may also keep a list of updates that have been applied to it.
|
|
31
|
+
This is useful for undo/redo functionality, especially in the context of
|
|
32
|
+
revising history. It is a good idea to accept a cache limit in your
|
|
33
|
+
constructor, and overwrite old updates. Here's an example of how we
|
|
34
|
+
might set that up:
|
|
35
|
+
|
|
36
|
+
myTransceiver = Transceiver {
|
|
37
|
+
cacheUpdateNumber: number = 27
|
|
38
|
+
cacheIdx: number = 1
|
|
39
|
+
cacheLimit: number = 3
|
|
40
|
+
cache: Array<Update> = [
|
|
41
|
+
26=add:"x"
|
|
42
|
+
27=del:"x" (current)
|
|
43
|
+
25=add:"y"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
CONFIRM/NO-OP
|
|
48
|
+
Update `27=del:"x"` is passed to myTransceiver.do:
|
|
49
|
+
- [updateNumber = 27, update = `del:"x"`]
|
|
50
|
+
- updateOffset = updateNumber - cacheUpdateNumber // 0
|
|
51
|
+
- eventOffset < 1 // true (we're validating the past)
|
|
52
|
+
- |eventOffset| < cacheLimit // true (we remember this update)
|
|
53
|
+
- eventIdx = cacheIdx + updateOffset // 1
|
|
54
|
+
- update === cache.get(eventIdx) // true
|
|
55
|
+
- return null // ๐
|
|
56
|
+
|
|
57
|
+
EXPECTED UPDATE
|
|
58
|
+
Update `28=add:"x"` is passed to myTransceiver.do:
|
|
59
|
+
- [updateNumber = 28, update = `add:"x"`]
|
|
60
|
+
- updateOffset = updateNumber - cacheUpdateNumber // 1
|
|
61
|
+
- eventOffset < 1 // false (we're in the future)
|
|
62
|
+
- eventOffset === 1 // true (we're ready to apply this update)
|
|
63
|
+
- cacheIdx += eventOffset // 2
|
|
64
|
+
- cacheIdx %= cacheLimit // 2
|
|
65
|
+
- cache[cacheIdx] = update // cache = <{ 0 => add:"x" }>
|
|
66
|
+
- return null // ๐
|
|
67
|
+
|
|
68
|
+
UNEXPECTED UPDATE
|
|
69
|
+
Update `29=del:"x"` is passed to myTransceiver.do:
|
|
70
|
+
- [updateNumber = 29, update = `del:"x"`]
|
|
71
|
+
- updateOffset = updateNumber - cacheUpdateNumber // 2
|
|
72
|
+
- eventOffset < 1 // false (we're in the future)
|
|
73
|
+
- eventOffset === 1 // false (we're NOT ready to apply this update)
|
|
74
|
+
- updateIdx := cacheIdx + updateOffset // 3
|
|
75
|
+
- updateIdx %= cacheLimit // 0
|
|
76
|
+
- cache[updateIdx] = update // cache = <{ 0 => del:"x" }>
|
|
77
|
+
- expectedUpdateNumber = cacheUpdateNumber + 1 // 28
|
|
78
|
+
- return expectedUpdateNumber // ๐คจ๐
|
|
79
|
+
|
|
80
|
+
SUCCESSFUL ROLLBACK UPDATE
|
|
81
|
+
Update `25=add:"z"` is passed to myTransceiver.do:
|
|
82
|
+
- [updateNumber = 25, update = `add:"z"`]
|
|
83
|
+
- updateOffset = updateNumber - cacheUpdateNumber // -2
|
|
84
|
+
- eventOffset < 1 // true (we're validating the past)
|
|
85
|
+
- |eventOffset| < cacheLimit // true (we remember this update)
|
|
86
|
+
- eventIdx = cacheIdx + updateOffset // -1
|
|
87
|
+
- eventIdx %= cacheLimit // 2
|
|
88
|
+
- update === cache[eventIdx] // false (we're rolling back)
|
|
89
|
+
- done := false
|
|
90
|
+
- update := cache[cacheIdx] // update = `del:"x"`
|
|
91
|
+
- undo(update) // myTransceiver.undo(`del:"x"`)
|
|
92
|
+
- while (!done) {
|
|
93
|
+
- cacheIdx -= 1 // 0, -1
|
|
94
|
+
- cacheIdx %= cacheLimit // 0, 2
|
|
95
|
+
- update = cache[cacheIdx] // update = `add:"y"`, `add:"x"`
|
|
96
|
+
- undo(update) // myTransceiver.undo(`add:"y"`), myTransceiver.undo(`add:"x"`)
|
|
97
|
+
- done = cacheIdx === eventIdx // false, true
|
|
98
|
+
- }
|
|
99
|
+
- do(update) // myTransceiver.do(`add:"z"`)
|
|
100
|
+
- return null // ๐
|
|
101
|
+
|
|
102
|
+
UNSUCCESSFUL ROLLBACK UPDATE
|
|
103
|
+
Update `24=add:"z"` is passed to myTransceiver.do:
|
|
104
|
+
- [updateNumber = 24, update = `add:"z"`]
|
|
105
|
+
- updateOffset = updateNumber - cacheUpdateNumber // -3
|
|
106
|
+
- eventOffset < 1 // true (we're validating the past)
|
|
107
|
+
- |eventOffset| < cacheLimit // 3 < 3 // false (we don't remember this update)
|
|
108
|
+
- return `OUT_OF_RANGE` // ๐ตโ๐ซ๐
|
|
109
|
+
|
|
110
|
+
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { StateToken } from "atom.io"
|
|
2
|
+
|
|
3
|
+
import type { Store } from "./store"
|
|
4
|
+
import { IMPLICIT } from "./store"
|
|
5
|
+
import { target } from "./transaction"
|
|
6
|
+
|
|
7
|
+
export type OperationProgress =
|
|
8
|
+
| {
|
|
9
|
+
open: false
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
open: true
|
|
13
|
+
done: Set<string>
|
|
14
|
+
prev: Map<string, any>
|
|
15
|
+
time: number
|
|
16
|
+
token: StateToken<any>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const openOperation = (token: StateToken<any>, store: Store): void => {
|
|
20
|
+
const core = target(store)
|
|
21
|
+
if (core.operation.open) {
|
|
22
|
+
store.config.logger?.error(
|
|
23
|
+
`โ failed to setState to "${token.key}" during a setState for "${core.operation.token.key}"`,
|
|
24
|
+
)
|
|
25
|
+
throw Symbol(`violation`)
|
|
26
|
+
}
|
|
27
|
+
core.operation = {
|
|
28
|
+
open: true,
|
|
29
|
+
done: new Set(),
|
|
30
|
+
prev: new Map(store.valueMap),
|
|
31
|
+
time: Date.now(),
|
|
32
|
+
token,
|
|
33
|
+
}
|
|
34
|
+
store.config.logger?.info(
|
|
35
|
+
`โญ operation start from "${token.key}" in store "${store.config.name}"${
|
|
36
|
+
store.transactionStatus.phase === `idle`
|
|
37
|
+
? ``
|
|
38
|
+
: ` ${store.transactionStatus.phase} "${store.transactionStatus.key}"`
|
|
39
|
+
}`,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
export const closeOperation = (store: Store): void => {
|
|
43
|
+
const core = target(store)
|
|
44
|
+
core.operation = { open: false }
|
|
45
|
+
store.config.logger?.info(`๐ด operation done`)
|
|
46
|
+
store.subject.operationStatus.next(core.operation)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const isDone = (key: string, store: Store = IMPLICIT.STORE): boolean => {
|
|
50
|
+
const core = target(store)
|
|
51
|
+
if (!core.operation.open) {
|
|
52
|
+
store.config.logger?.warn(
|
|
53
|
+
`isDone called outside of an operation. This is probably a bug.`,
|
|
54
|
+
)
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
return core.operation.done.has(key)
|
|
58
|
+
}
|
|
59
|
+
export const markDone = (key: string, store: Store = IMPLICIT.STORE): void => {
|
|
60
|
+
const core = target(store)
|
|
61
|
+
if (!core.operation.open) {
|
|
62
|
+
store.config.logger?.warn(
|
|
63
|
+
`markDone called outside of an operation. This is probably a bug.`,
|
|
64
|
+
)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
core.operation.done.add(key)
|
|
68
|
+
}
|