atom.io 0.4.1 → 0.6.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 +38 -10
- package/dist/index.d.mts +614 -0
- package/dist/index.d.ts +130 -77
- package/dist/index.js +584 -347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +582 -347
- package/dist/index.mjs.map +1 -1
- package/json/dist/index.d.mts +18 -0
- package/json/dist/index.d.ts +18 -0
- package/json/dist/index.js +51 -0
- package/json/dist/index.js.map +1 -0
- package/json/dist/index.mjs +15 -0
- package/json/dist/index.mjs.map +1 -0
- package/json/package.json +15 -0
- package/package.json +43 -9
- package/react/dist/index.d.mts +24 -0
- package/react/dist/index.d.ts +18 -11
- package/react/dist/index.js +45 -21
- package/react/dist/index.js.map +1 -1
- package/react/dist/index.mjs +31 -21
- package/react/dist/index.mjs.map +1 -1
- package/react-devtools/dist/index.d.mts +15 -0
- package/react-devtools/dist/index.d.ts +4 -4
- package/react-devtools/dist/index.js +1 -1
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/dist/index.mjs +1 -1
- package/react-devtools/dist/index.mjs.map +1 -1
- package/realtime/dist/index.d.mts +27 -0
- package/realtime/dist/index.d.ts +27 -0
- package/realtime/dist/index.js +191 -0
- package/realtime/dist/index.js.map +1 -0
- package/realtime/dist/index.mjs +152 -0
- package/realtime/dist/index.mjs.map +1 -0
- package/realtime/package.json +15 -0
- package/realtime-react/dist/index.d.mts +45 -0
- package/realtime-react/dist/index.d.ts +45 -0
- package/realtime-react/dist/index.js +213 -0
- package/realtime-react/dist/index.js.map +1 -0
- package/realtime-react/dist/index.mjs +168 -0
- package/realtime-react/dist/index.mjs.map +1 -0
- package/realtime-react/package.json +15 -0
- package/src/index.ts +21 -5
- package/src/internal/atom-internal.ts +1 -1
- package/src/internal/families-internal.ts +3 -3
- package/src/internal/get.ts +39 -15
- package/src/internal/index.ts +2 -0
- package/src/internal/meta/meta-state.ts +1 -1
- package/src/internal/operation.ts +3 -1
- package/src/internal/selector/create-read-write-selector.ts +62 -0
- package/src/internal/selector/create-readonly-selector.ts +52 -0
- package/src/internal/selector/index.ts +4 -0
- package/src/internal/selector/lookup-selector-sources.ts +16 -0
- package/src/internal/selector/register-selector.ts +57 -0
- package/src/internal/selector/trace-selector-atoms.ts +43 -0
- package/src/internal/selector/update-selector-atoms.ts +33 -0
- package/src/internal/selector-internal.ts +9 -197
- package/src/internal/set.ts +1 -1
- package/src/internal/store.ts +44 -17
- package/src/internal/subscribe-internal.ts +6 -1
- package/src/internal/time-travel-internal.ts +7 -7
- package/src/internal/timeline/add-atom-to-timeline.ts +164 -0
- package/src/internal/timeline/index.ts +1 -0
- package/src/internal/timeline-internal.ts +39 -146
- package/src/internal/transaction/abort-transaction.ts +12 -0
- package/src/internal/transaction/apply-transaction.ts +54 -0
- package/src/internal/transaction/build-transaction.ts +33 -0
- package/src/internal/transaction/index.ts +25 -0
- package/src/internal/transaction/redo-transaction.ts +23 -0
- package/src/internal/transaction/undo-transaction.ts +23 -0
- package/src/internal/transaction-internal.ts +16 -133
- package/src/json/index.ts +1 -0
- package/src/json/select-json.ts +18 -0
- package/src/react/index.ts +2 -46
- package/src/react/store-context.tsx +14 -0
- package/src/react/store-hooks.ts +48 -0
- package/src/react-devtools/AtomIODevtools.tsx +1 -1
- package/src/react-explorer/AtomIOExplorer.tsx +2 -2
- package/src/react-explorer/explorer-states.ts +5 -5
- package/src/react-explorer/index.ts +1 -1
- package/src/react-explorer/space-states.ts +8 -9
- package/src/realtime/README.md +33 -0
- package/src/realtime/hook-composition/expose-family.ts +101 -0
- package/src/realtime/hook-composition/expose-single.ts +38 -0
- package/src/realtime/hook-composition/index.ts +12 -0
- package/src/realtime/hook-composition/receive-state.ts +29 -0
- package/src/realtime/hook-composition/receive-transaction.ts +18 -0
- package/src/realtime/index.ts +1 -0
- package/src/realtime-react/index.ts +3 -0
- package/src/realtime-react/realtime-context.tsx +31 -0
- package/src/realtime-react/realtime-hooks.ts +39 -0
- package/src/realtime-react/realtime-state.ts +10 -0
- package/src/realtime-react/use-pull-family-member.ts +27 -0
- package/src/realtime-react/use-pull-family.ts +25 -0
- package/src/realtime-react/use-pull.ts +23 -0
- package/src/realtime-react/use-push.ts +26 -0
- package/src/realtime-react/use-server-action.ts +34 -0
- package/src/selector.ts +9 -6
- package/src/silo.ts +53 -0
- package/src/subscribe.ts +42 -2
- package/src/timeline.ts +10 -0
- package/src/transaction.ts +24 -12
|
@@ -1,143 +1,25 @@
|
|
|
1
1
|
import HAMT from "hamt_plus"
|
|
2
2
|
import * as Rx from "rxjs"
|
|
3
3
|
|
|
4
|
+
import type { ƒn } from "~/packages/anvl/src/function"
|
|
5
|
+
|
|
4
6
|
import type { Store, StoreCore } from "."
|
|
5
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
abortTransaction,
|
|
9
|
+
applyTransaction,
|
|
10
|
+
buildTransaction,
|
|
11
|
+
deposit,
|
|
12
|
+
IMPLICIT,
|
|
13
|
+
} from "."
|
|
14
|
+
import type { TransactionOptions, TransactionToken, TransactionUpdate } from ".."
|
|
6
15
|
import { getState, setState } from ".."
|
|
7
|
-
import type {
|
|
8
|
-
AtomToken,
|
|
9
|
-
StateUpdate,
|
|
10
|
-
Transaction,
|
|
11
|
-
TransactionOptions,
|
|
12
|
-
TransactionToken,
|
|
13
|
-
ƒn,
|
|
14
|
-
} from ".."
|
|
15
|
-
|
|
16
|
-
export const TRANSACTION_PHASES = [`idle`, `building`, `applying`] as const
|
|
17
|
-
export type TransactionPhase = (typeof TRANSACTION_PHASES)[number]
|
|
18
16
|
|
|
19
|
-
export type
|
|
20
|
-
key: string
|
|
21
|
-
}
|
|
22
|
-
export type TransactionUpdate<ƒ extends ƒn> = {
|
|
17
|
+
export type Transaction<ƒ extends ƒn> = {
|
|
23
18
|
key: string
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export type TransactionUpdateInProgress<ƒ extends ƒn> = TransactionUpdate<ƒ> & {
|
|
30
|
-
phase: `applying` | `building`
|
|
31
|
-
core: StoreCore
|
|
32
|
-
}
|
|
33
|
-
export type TransactionIdle = {
|
|
34
|
-
phase: `idle`
|
|
35
|
-
}
|
|
36
|
-
export type TransactionStatus<ƒ extends ƒn> =
|
|
37
|
-
| TransactionIdle
|
|
38
|
-
| TransactionUpdateInProgress<ƒ>
|
|
39
|
-
|
|
40
|
-
export const buildTransaction = (
|
|
41
|
-
key: string,
|
|
42
|
-
params: any[],
|
|
43
|
-
store: Store
|
|
44
|
-
): void => {
|
|
45
|
-
store.transactionStatus = {
|
|
46
|
-
key,
|
|
47
|
-
phase: `building`,
|
|
48
|
-
core: {
|
|
49
|
-
atoms: store.atoms,
|
|
50
|
-
atomsThatAreDefault: store.atomsThatAreDefault,
|
|
51
|
-
operation: { open: false },
|
|
52
|
-
readonlySelectors: store.readonlySelectors,
|
|
53
|
-
timelines: store.timelines,
|
|
54
|
-
timelineAtoms: store.timelineAtoms,
|
|
55
|
-
transactions: store.transactions,
|
|
56
|
-
selectorAtoms: store.selectorAtoms,
|
|
57
|
-
selectorGraph: store.selectorGraph,
|
|
58
|
-
selectors: store.selectors,
|
|
59
|
-
valueMap: store.valueMap,
|
|
60
|
-
},
|
|
61
|
-
atomUpdates: [],
|
|
62
|
-
params,
|
|
63
|
-
output: undefined,
|
|
64
|
-
}
|
|
65
|
-
store.config.logger?.info(`🛫`, `transaction "${key}" started`)
|
|
66
|
-
}
|
|
67
|
-
export const applyTransaction = <ƒ extends ƒn>(
|
|
68
|
-
output: ReturnType<ƒ>,
|
|
69
|
-
store: Store
|
|
70
|
-
): void => {
|
|
71
|
-
if (store.transactionStatus.phase !== `building`) {
|
|
72
|
-
store.config.logger?.warn(
|
|
73
|
-
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
74
|
-
)
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
store.config.logger?.info(
|
|
78
|
-
`🛃 apply transaction "${store.transactionStatus.key}"`
|
|
79
|
-
)
|
|
80
|
-
store.transactionStatus.phase = `applying`
|
|
81
|
-
store.transactionStatus.output = output
|
|
82
|
-
const { atomUpdates } = store.transactionStatus
|
|
83
|
-
|
|
84
|
-
for (const { key, newValue } of atomUpdates) {
|
|
85
|
-
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
86
|
-
if (!HAMT.has(token.key, store.valueMap)) {
|
|
87
|
-
const atom = HAMT.get(token.key, store.transactionStatus.core.atoms)
|
|
88
|
-
store.atoms = HAMT.set(atom.key, atom, store.atoms)
|
|
89
|
-
store.valueMap = HAMT.set(atom.key, atom.default, store.valueMap)
|
|
90
|
-
store.config.logger?.info(`🔧`, `add atom "${atom.key}"`)
|
|
91
|
-
}
|
|
92
|
-
const state = withdraw(token, store)
|
|
93
|
-
|
|
94
|
-
setState(state, newValue, store)
|
|
95
|
-
}
|
|
96
|
-
const myTransaction = withdraw<ƒ>(
|
|
97
|
-
{ key: store.transactionStatus.key, type: `transaction` },
|
|
98
|
-
store
|
|
99
|
-
)
|
|
100
|
-
myTransaction.subject.next({
|
|
101
|
-
key: store.transactionStatus.key,
|
|
102
|
-
atomUpdates,
|
|
103
|
-
output,
|
|
104
|
-
params: store.transactionStatus.params as Parameters<ƒ>,
|
|
105
|
-
})
|
|
106
|
-
store.transactionStatus = { phase: `idle` }
|
|
107
|
-
store.config.logger?.info(`🛬`, `transaction done`)
|
|
108
|
-
}
|
|
109
|
-
export const undoTransactionUpdate = <ƒ extends ƒn>(
|
|
110
|
-
update: TransactionUpdate<ƒ>,
|
|
111
|
-
store: Store
|
|
112
|
-
): void => {
|
|
113
|
-
store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
|
|
114
|
-
for (const { key, oldValue } of update.atomUpdates) {
|
|
115
|
-
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
116
|
-
const state = withdraw(token, store)
|
|
117
|
-
setState(state, oldValue, store)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
export const redoTransactionUpdate = <ƒ extends ƒn>(
|
|
121
|
-
update: TransactionUpdate<ƒ>,
|
|
122
|
-
store: Store
|
|
123
|
-
): void => {
|
|
124
|
-
store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
|
|
125
|
-
for (const { key, newValue } of update.atomUpdates) {
|
|
126
|
-
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
127
|
-
const state = withdraw(token, store)
|
|
128
|
-
setState(state, newValue, store)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const abortTransaction = (store: Store): void => {
|
|
133
|
-
if (store.transactionStatus.phase === `idle`) {
|
|
134
|
-
store.config.logger?.warn(
|
|
135
|
-
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
136
|
-
)
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
store.transactionStatus = { phase: `idle` }
|
|
140
|
-
store.config.logger?.info(`🪂`, `transaction fail`)
|
|
19
|
+
type: `transaction`
|
|
20
|
+
install: (store: Store) => void
|
|
21
|
+
subject: Rx.Subject<TransactionUpdate<ƒ>>
|
|
22
|
+
run: (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
|
|
141
23
|
}
|
|
142
24
|
|
|
143
25
|
export function transaction__INTERNAL<ƒ extends ƒn>(
|
|
@@ -165,6 +47,7 @@ export function transaction__INTERNAL<ƒ extends ƒn>(
|
|
|
165
47
|
throw thrown
|
|
166
48
|
}
|
|
167
49
|
},
|
|
50
|
+
install: (store) => transaction__INTERNAL(options, store),
|
|
168
51
|
subject: new Rx.Subject(),
|
|
169
52
|
}
|
|
170
53
|
const core = target(store)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./select-json"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as AtomIO from "atom.io"
|
|
2
|
+
|
|
3
|
+
import type { Json, JsonInterface } from "~/packages/anvl/src/json"
|
|
4
|
+
|
|
5
|
+
export const selectJson = <T, J extends Json>(
|
|
6
|
+
atom: AtomIO.AtomToken<T>,
|
|
7
|
+
transform: JsonInterface<T, J>,
|
|
8
|
+
store: AtomIO.Store = AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
9
|
+
): AtomIO.SelectorToken<J> =>
|
|
10
|
+
AtomIO.__INTERNAL__.selector__INTERNAL(
|
|
11
|
+
{
|
|
12
|
+
key: `${atom.key}JSON`,
|
|
13
|
+
get: ({ get }) => transform.toJson(get(atom)),
|
|
14
|
+
set: ({ set }, newValue) => set(atom, transform.fromJson(newValue)),
|
|
15
|
+
},
|
|
16
|
+
undefined,
|
|
17
|
+
store
|
|
18
|
+
)
|
package/src/react/index.ts
CHANGED
|
@@ -1,46 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { subscribe, setState, __INTERNAL__, getState } from "atom.io"
|
|
4
|
-
import type { ReadonlySelectorToken, StateToken } from "atom.io"
|
|
5
|
-
|
|
6
|
-
import type { Modifier } from "~/packages/anvl/src/function"
|
|
7
|
-
|
|
8
|
-
export type StoreHooks = {
|
|
9
|
-
useI: <T>(token: StateToken<T>) => (next: Modifier<T> | T) => void
|
|
10
|
-
useO: <T>(token: ReadonlySelectorToken<T> | StateToken<T>) => T
|
|
11
|
-
useIO: <T>(token: StateToken<T>) => [T, (next: Modifier<T> | T) => void]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const composeStoreHooks = (
|
|
15
|
-
store: __INTERNAL__.Store = __INTERNAL__.IMPLICIT.STORE
|
|
16
|
-
): StoreHooks => {
|
|
17
|
-
function useI<T>(token: StateToken<T>): (next: Modifier<T> | T) => void {
|
|
18
|
-
const updateState = (next: Modifier<T> | T) => setState(token, next, store)
|
|
19
|
-
return updateState
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function useO<T>(token: ReadonlySelectorToken<T> | StateToken<T>): T {
|
|
23
|
-
return useSyncExternalStore<T>(
|
|
24
|
-
(observe) => subscribe(token, observe, store),
|
|
25
|
-
() => getState(token, store)
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function useIO<T>(token: StateToken<T>): [T, (next: Modifier<T> | T) => void] {
|
|
30
|
-
return [useO(token), useI(token)]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { useI, useO, useIO }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const { useI, useO, useIO } = composeStoreHooks()
|
|
37
|
-
|
|
38
|
-
export function useStore<T>(
|
|
39
|
-
token: StateToken<T>
|
|
40
|
-
): [T, (next: Modifier<T> | T) => void]
|
|
41
|
-
export function useStore<T>(token: ReadonlySelectorToken<T>): T
|
|
42
|
-
export function useStore<T>(
|
|
43
|
-
token: ReadonlySelectorToken<T> | StateToken<T>
|
|
44
|
-
): T | [T, (next: Modifier<T> | T) => void] {
|
|
45
|
-
return token.type === `readonly_selector` ? useO(token) : useIO(token)
|
|
46
|
-
}
|
|
1
|
+
export * from "./store-context"
|
|
2
|
+
export * from "./store-hooks"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
export const StoreContext = React.createContext<AtomIO.Store>(
|
|
6
|
+
AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
export const StoreProvider: React.FC<{
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
store?: AtomIO.Store
|
|
12
|
+
}> = ({ children, store = AtomIO.__INTERNAL__.IMPLICIT.STORE }) => (
|
|
13
|
+
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
|
|
14
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { Modifier } from "~/packages/anvl/src/function"
|
|
6
|
+
|
|
7
|
+
import { StoreContext } from "./store-context"
|
|
8
|
+
|
|
9
|
+
export type StoreHooks = {
|
|
10
|
+
useI: <T>(token: AtomIO.StateToken<T>) => (next: Modifier<T> | T) => void
|
|
11
|
+
useO: <T>(token: AtomIO.ReadonlySelectorToken<T> | AtomIO.StateToken<T>) => T
|
|
12
|
+
useIO: <T>(token: AtomIO.StateToken<T>) => [T, (next: Modifier<T> | T) => void]
|
|
13
|
+
}
|
|
14
|
+
export const storeHooks: StoreHooks = { useI, useO, useIO }
|
|
15
|
+
|
|
16
|
+
export function useI<T>(
|
|
17
|
+
token: AtomIO.StateToken<T>
|
|
18
|
+
): (next: Modifier<T> | T) => void {
|
|
19
|
+
const store = React.useContext(StoreContext)
|
|
20
|
+
const update = (next: Modifier<T> | T) => AtomIO.setState(token, next, store)
|
|
21
|
+
return update
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useO<T>(
|
|
25
|
+
token: AtomIO.ReadonlySelectorToken<T> | AtomIO.StateToken<T>
|
|
26
|
+
): T {
|
|
27
|
+
const store = React.useContext(StoreContext)
|
|
28
|
+
return React.useSyncExternalStore<T>(
|
|
29
|
+
(observe) => AtomIO.subscribe(token, observe, store),
|
|
30
|
+
() => AtomIO.getState(token, store)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useIO<T>(
|
|
35
|
+
token: AtomIO.StateToken<T>
|
|
36
|
+
): [T, (next: Modifier<T> | T) => void] {
|
|
37
|
+
return [useO(token), useI(token)]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useStore<T>(
|
|
41
|
+
token: AtomIO.StateToken<T>
|
|
42
|
+
): [T, (next: Modifier<T> | T) => void]
|
|
43
|
+
export function useStore<T>(token: AtomIO.ReadonlySelectorToken<T>): T
|
|
44
|
+
export function useStore<T>(
|
|
45
|
+
token: AtomIO.ReadonlySelectorToken<T> | AtomIO.StateToken<T>
|
|
46
|
+
): T | [T, (next: Modifier<T> | T) => void] {
|
|
47
|
+
return token.type === `readonly_selector` ? useO(token) : useIO(token)
|
|
48
|
+
}
|
|
@@ -2,8 +2,8 @@ import type { FC } from "react"
|
|
|
2
2
|
import { useRef } from "react"
|
|
3
3
|
|
|
4
4
|
import { atom, __INTERNAL__ } from "atom.io"
|
|
5
|
-
import { useI, useO, useIO } from "atom.io/react"
|
|
6
5
|
import type { StoreHooks } from "atom.io/react"
|
|
6
|
+
import { useI, useO, useIO } from "atom.io/react"
|
|
7
7
|
import { LayoutGroup, motion, spring } from "framer-motion"
|
|
8
8
|
|
|
9
9
|
import { TokenList } from "./TokenList"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { FC, ReactNode } from "react"
|
|
2
2
|
import { useEffect } from "react"
|
|
3
3
|
|
|
4
|
+
import type { StoreHooks } from "atom.io/react"
|
|
4
5
|
import { Link, MemoryRouter, useLocation } from "react-router-dom"
|
|
5
6
|
|
|
6
|
-
import type { composeStoreHooks } from "~/packages/atom.io/src/react"
|
|
7
7
|
import { ErrorBoundary } from "~/packages/hamr/src/react-error-boundary"
|
|
8
8
|
import type { WC } from "~/packages/hamr/src/react-json-editor"
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ export type ExplorerOptions = {
|
|
|
17
17
|
SpaceWrapper: WC
|
|
18
18
|
CloseSpaceButton: FC<{ onClick: () => void }>
|
|
19
19
|
}
|
|
20
|
-
storeHooks:
|
|
20
|
+
storeHooks: StoreHooks
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const DEFAULT_COMPONENTS: ExplorerOptions[`Components`] = {
|
|
@@ -2,7 +2,6 @@ import { lastOf } from "~/packages/anvl/src/array"
|
|
|
2
2
|
import { now } from "~/packages/anvl/src/id"
|
|
3
3
|
import { Join } from "~/packages/anvl/src/join"
|
|
4
4
|
import type { Entries } from "~/packages/anvl/src/object"
|
|
5
|
-
import { cannotExist } from "~/packages/anvl/src/refinement"
|
|
6
5
|
|
|
7
6
|
import { addToIndex, removeFromIndex } from "."
|
|
8
7
|
import {
|
|
@@ -21,9 +20,7 @@ import type {
|
|
|
21
20
|
AtomFamily,
|
|
22
21
|
AtomToken,
|
|
23
22
|
ReadonlySelectorFamily,
|
|
24
|
-
ReadonlySelectorToken,
|
|
25
23
|
SelectorFamily,
|
|
26
|
-
TransactionToken,
|
|
27
24
|
Write,
|
|
28
25
|
} from ".."
|
|
29
26
|
import { selectorFamily, selector, transaction, atom } from ".."
|
|
@@ -39,14 +36,17 @@ export const makeViewsPerSpaceState = (
|
|
|
39
36
|
persistAtom<Join<null, `viewId`, `spaceId`>>(localStorage)({
|
|
40
37
|
stringify: (index) => JSON.stringify(index.toJSON()),
|
|
41
38
|
parse: (json) =>
|
|
42
|
-
Join.fromJSON(JSON.parse(json),
|
|
39
|
+
Join.fromJSON(JSON.parse(json), {
|
|
40
|
+
from: `viewId`,
|
|
41
|
+
to: `spaceId`,
|
|
42
|
+
}),
|
|
43
43
|
})(`${key}:views_per_space`),
|
|
44
44
|
],
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
export const makeSpaceViewsFamily = (
|
|
48
48
|
key: string,
|
|
49
|
-
viewsPerSpaceState: AtomToken<Join
|
|
49
|
+
viewsPerSpaceState: AtomToken<Join<null, `viewId`, `spaceId`>>
|
|
50
50
|
): ReadonlySelectorFamily<string[], string> =>
|
|
51
51
|
selectorFamily<string[], string>({
|
|
52
52
|
key: `${key}:space_views`,
|
|
@@ -5,8 +5,8 @@ import { parseJson, stringifyJson } from "~/packages/anvl/src/json"
|
|
|
5
5
|
import { hasExactProperties } from "~/packages/anvl/src/object"
|
|
6
6
|
|
|
7
7
|
import { persistStringSetAtom } from "./explorer-effects"
|
|
8
|
-
import type { AtomToken, ReadonlySelectorFamily
|
|
9
|
-
import { selectorFamily } from ".."
|
|
8
|
+
import type { AtomToken, ReadonlySelectorFamily } from ".."
|
|
9
|
+
import { SelectorFamily, selectorFamily } from ".."
|
|
10
10
|
import type { AtomFamily } from "../atom"
|
|
11
11
|
import { atom, atomFamily } from "../atom"
|
|
12
12
|
import { lazyLocalStorageEffect, persistAtom } from "../web-effects"
|
|
@@ -30,12 +30,11 @@ export const makeSpaceLayoutState = (
|
|
|
30
30
|
parse: (string) => {
|
|
31
31
|
try {
|
|
32
32
|
const json = parseJson(string)
|
|
33
|
-
const join = Join.fromJSON(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
)
|
|
33
|
+
const join = Join.fromJSON(json, {
|
|
34
|
+
isContent: hasExactProperties({ size: isNumber }),
|
|
35
|
+
from: `parent`,
|
|
36
|
+
to: `child`,
|
|
37
|
+
})
|
|
39
38
|
return join
|
|
40
39
|
} catch (thrown) {
|
|
41
40
|
console.error(`Error parsing spaceLayoutState from localStorage`)
|
|
@@ -48,7 +47,7 @@ export const makeSpaceLayoutState = (
|
|
|
48
47
|
|
|
49
48
|
export const makeSpaceLayoutNodeFamily = (
|
|
50
49
|
key: string,
|
|
51
|
-
spaceLayoutState: AtomToken<Join<{ size: number }
|
|
50
|
+
spaceLayoutState: AtomToken<Join<{ size: number }, `parent`, `child`>>
|
|
52
51
|
): ReadonlySelectorFamily<{ childSpaceIds: string[]; size: number }, string> =>
|
|
53
52
|
selectorFamily<{ childSpaceIds: string[]; size: number }, string>({
|
|
54
53
|
key: `${key}:explorer_space`,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CLIENT ACTS AND REPORTS
|
|
2
|
+
- [x] input event fires
|
|
3
|
+
- [x] event handler runs transaction
|
|
4
|
+
- [x] client store updates optimistically
|
|
5
|
+
- [ ] on success
|
|
6
|
+
- [ ] client generates transactionId and optimistic TransactionUpdate
|
|
7
|
+
- [ ] client pushes TransactionUpdate to TimelineData.history
|
|
8
|
+
- [ ] client sets TransactionUpdate in optimisticTransactions map by transactionId
|
|
9
|
+
- [ ] client emits TransactionRequest { key, params, transactionId }
|
|
10
|
+
|
|
11
|
+
# SERVER VALIDATES, INTEGRATES, AND BROADCASTS
|
|
12
|
+
## use
|
|
13
|
+
- [x] server receives TransactionRequest
|
|
14
|
+
- `{ key, params, transactionId }`
|
|
15
|
+
- [ ] verify `transactionId` is unique
|
|
16
|
+
- [ ] server adds timestamp to `TransactionRequest`
|
|
17
|
+
- `{ key, params, transactionId, timestamp }`
|
|
18
|
+
- [ ] server runs transaction, computing `TransactionUpdate` in the process
|
|
19
|
+
- [ ] emit `TransactionUpdate`
|
|
20
|
+
- `{ key, params, transactionId, timestamp, atomUpdates, output }`
|
|
21
|
+
- [ ] server adds `TransactionUpdate` to TimelineData.history
|
|
22
|
+
|
|
23
|
+
# CLIENT BEHOLDS AND REACTS
|
|
24
|
+
- [ ] client receives official TransactionUpdate
|
|
25
|
+
- [ ] client retrieves its own TransactionUpdate from optimisticTransactions map
|
|
26
|
+
- [ ] client compares official and optimistic TransactionUpdates
|
|
27
|
+
- [ ] (stringify atomUpdates and compare strict)
|
|
28
|
+
- [ ] if match, client removes TransactionUpdate from optimisticTransactions map
|
|
29
|
+
- [ ] if mismatch
|
|
30
|
+
- [ ] client undoes timeline until it finds its own TransactionUpdate
|
|
31
|
+
- [ ] client replaces its own TransactionUpdate with official TransactionUpdate
|
|
32
|
+
- [ ] client removes its own TransactionUpdate from optimisticTransactions map
|
|
33
|
+
- [ ] client redoes timeline until it reaches the "HEAD"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Json } from "anvl/json"
|
|
2
|
+
import { parseJson } from "anvl/json"
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { ServerConfig } from ".."
|
|
6
|
+
|
|
7
|
+
const subscribeToTokenCreation = <T>(
|
|
8
|
+
family: AtomIO.AtomFamily<T> | AtomIO.SelectorFamily<T>,
|
|
9
|
+
handleTokenCreation: (token: AtomIO.StateToken<T>) => void
|
|
10
|
+
): (() => void) => {
|
|
11
|
+
const subscription =
|
|
12
|
+
family.type === `atom_family`
|
|
13
|
+
? family.subject.subscribe(handleTokenCreation)
|
|
14
|
+
: family.subject.subscribe(handleTokenCreation)
|
|
15
|
+
return () => subscription.unsubscribe()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
19
|
+
export const useExposeFamily = ({ socket, store }: ServerConfig) => {
|
|
20
|
+
return function exposeFamily<J extends Json>(
|
|
21
|
+
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>,
|
|
22
|
+
index: AtomIO.StateToken<Set<string>>
|
|
23
|
+
): () => void {
|
|
24
|
+
const unsubSingleCallbacksByKey = new Map<string, () => void>()
|
|
25
|
+
const unsubFamilyCallbacksByKey = new Map<string, () => void>()
|
|
26
|
+
|
|
27
|
+
const fillFamilyUnsubRequest = () => {
|
|
28
|
+
unsubFamilyCallbacksByKey.forEach((unsub) => unsub())
|
|
29
|
+
unsubFamilyCallbacksByKey.clear()
|
|
30
|
+
socket.off(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fillSingleUnsubRequest = (key: string) => {
|
|
34
|
+
socket.off(`unsub:${key}`, fillSingleUnsubRequest)
|
|
35
|
+
const unsub = unsubSingleCallbacksByKey.get(key)
|
|
36
|
+
if (unsub) {
|
|
37
|
+
unsub()
|
|
38
|
+
unsubSingleCallbacksByKey.delete(key)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fillSubRequest = (subKey?: AtomIO.Serializable) => {
|
|
43
|
+
if (subKey === undefined) {
|
|
44
|
+
const keys = AtomIO.getState(index, store)
|
|
45
|
+
keys.forEach((key) => {
|
|
46
|
+
const token = family(key)
|
|
47
|
+
socket.emit(
|
|
48
|
+
`serve:${family.key}`,
|
|
49
|
+
parseJson(token.family?.subKey || `null`),
|
|
50
|
+
AtomIO.getState(token, store)
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const unsubscribeFromTokenCreation = subscribeToTokenCreation(
|
|
55
|
+
family,
|
|
56
|
+
(token) => {
|
|
57
|
+
const unsub = AtomIO.subscribe(
|
|
58
|
+
token,
|
|
59
|
+
({ newValue }) => {
|
|
60
|
+
socket.emit(
|
|
61
|
+
`serve:${family.key}`,
|
|
62
|
+
parseJson(token.family?.subKey || `null`),
|
|
63
|
+
newValue
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
store
|
|
67
|
+
)
|
|
68
|
+
unsubFamilyCallbacksByKey.set(token.key, unsub)
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
unsubFamilyCallbacksByKey.set(family.key, unsubscribeFromTokenCreation)
|
|
72
|
+
|
|
73
|
+
socket.on(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
74
|
+
} else {
|
|
75
|
+
const token = family(subKey)
|
|
76
|
+
socket.emit(`serve:${token.key}`, AtomIO.getState(token, store))
|
|
77
|
+
const unsubscribe = AtomIO.subscribe(
|
|
78
|
+
token,
|
|
79
|
+
({ newValue }) => {
|
|
80
|
+
socket.emit(`serve:${token.key}`, newValue)
|
|
81
|
+
},
|
|
82
|
+
store
|
|
83
|
+
)
|
|
84
|
+
unsubSingleCallbacksByKey.set(token.key, unsubscribe)
|
|
85
|
+
socket.on(`unsub:${token.key}`, () => {
|
|
86
|
+
fillSingleUnsubRequest(token.key)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
socket.on(`sub:${family.key}`, fillSubRequest)
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
socket.off(`sub:${family.key}`, fillSubRequest)
|
|
95
|
+
unsubFamilyCallbacksByKey.forEach((unsub) => unsub())
|
|
96
|
+
unsubSingleCallbacksByKey.forEach((unsub) => unsub())
|
|
97
|
+
unsubFamilyCallbacksByKey.clear()
|
|
98
|
+
unsubSingleCallbacksByKey.clear()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Json } from "anvl/json"
|
|
2
|
+
import * as AtomIO from "atom.io"
|
|
3
|
+
|
|
4
|
+
import type { ServerConfig } from ".."
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
7
|
+
export const useExposeSingle = ({ socket, store }: ServerConfig) => {
|
|
8
|
+
return function exposeSingle<J extends Json>(
|
|
9
|
+
token: AtomIO.StateToken<J>
|
|
10
|
+
): () => void {
|
|
11
|
+
let unsubscribeFromStateUpdates: (() => void) | null = null
|
|
12
|
+
|
|
13
|
+
const fillUnsubRequest = () => {
|
|
14
|
+
socket.off(`unsub:${token.key}`, fillUnsubRequest)
|
|
15
|
+
unsubscribeFromStateUpdates?.()
|
|
16
|
+
unsubscribeFromStateUpdates = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const fillSubRequest = () => {
|
|
20
|
+
socket.emit(`serve:${token.key}`, AtomIO.getState(token, store))
|
|
21
|
+
unsubscribeFromStateUpdates = AtomIO.subscribe(
|
|
22
|
+
token,
|
|
23
|
+
({ newValue }) => {
|
|
24
|
+
socket.emit(`serve:${token.key}`, newValue)
|
|
25
|
+
},
|
|
26
|
+
store
|
|
27
|
+
)
|
|
28
|
+
socket.on(`unsub:${token.key}`, fillUnsubRequest)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
socket.on(`sub:${token.key}`, fillSubRequest)
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
socket.off(`sub:${token.key}`, fillSubRequest)
|
|
35
|
+
unsubscribeFromStateUpdates?.()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import type * as SocketIO from "socket.io"
|
|
3
|
+
|
|
4
|
+
export * from "./expose-single"
|
|
5
|
+
export * from "./expose-family"
|
|
6
|
+
export * from "./receive-transaction"
|
|
7
|
+
export * from "./receive-state"
|
|
8
|
+
|
|
9
|
+
export type ServerConfig = {
|
|
10
|
+
socket: SocketIO.Socket
|
|
11
|
+
store?: AtomIO.__INTERNAL__.Store
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Json } from "anvl/json"
|
|
2
|
+
import * as AtomIO from "atom.io"
|
|
3
|
+
|
|
4
|
+
import type { ServerConfig } from ".."
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
7
|
+
export const useReceiveState = ({ socket, store }: ServerConfig) => {
|
|
8
|
+
return function receiveState<J extends Json>(
|
|
9
|
+
token: AtomIO.StateToken<J>
|
|
10
|
+
): () => void {
|
|
11
|
+
const publish = (newValue: J) => AtomIO.setState(token, newValue, store)
|
|
12
|
+
|
|
13
|
+
const fillPubUnclaim = () => {
|
|
14
|
+
socket.off(`pub:${token.key}`, publish)
|
|
15
|
+
socket.off(`unclaim:${token.key}`, fillPubUnclaim)
|
|
16
|
+
}
|
|
17
|
+
const fillPubClaim = () => {
|
|
18
|
+
socket.on(`pub:${token.key}`, publish)
|
|
19
|
+
socket.on(`unclaim:${token.key}`, fillPubUnclaim)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
socket.on(`claim:${token.key}`, fillPubClaim)
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
socket.off(`claim:${token.key}`, fillPubClaim)
|
|
26
|
+
socket.off(`pub:${token.key}`, publish)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as AtomIO from "atom.io"
|
|
2
|
+
|
|
3
|
+
import type { ƒn } from "~/packages/anvl/src/function"
|
|
4
|
+
|
|
5
|
+
import type { ServerConfig } from "."
|
|
6
|
+
|
|
7
|
+
export const useReceiveTransaction = ({ socket, store }: ServerConfig) => {
|
|
8
|
+
return function receiveTransaction<ƒ extends ƒn>(
|
|
9
|
+
tx: AtomIO.TransactionToken<ƒ>
|
|
10
|
+
): () => void {
|
|
11
|
+
const fillTransactionRequest = (update: AtomIO.TransactionUpdate<ƒ>) =>
|
|
12
|
+
AtomIO.runTransaction<ƒ>(tx, store)(...update.params)
|
|
13
|
+
|
|
14
|
+
socket.on(`tx:${tx.key}`, fillTransactionRequest)
|
|
15
|
+
|
|
16
|
+
return () => socket.off(`tx:${tx.key}`, fillTransactionRequest)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./hook-composition"
|