atom.io 0.5.0 → 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/dist/index.d.mts +82 -66
- package/dist/index.d.ts +82 -66
- package/dist/index.js +482 -360
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +481 -360
- package/dist/index.mjs.map +1 -1
- package/json/dist/index.js.map +1 -1
- package/json/dist/index.mjs.map +1 -1
- package/package.json +12 -5
- package/react/dist/index.d.mts +18 -11
- 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 +4 -4
- package/react-devtools/dist/index.d.ts +4 -4
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/dist/index.mjs.map +1 -1
- package/realtime/dist/index.d.mts +3 -1
- package/realtime/dist/index.d.ts +3 -1
- package/realtime/dist/index.js +23 -0
- package/realtime/dist/index.js.map +1 -1
- package/realtime/dist/index.mjs +22 -0
- package/realtime/dist/index.mjs.map +1 -1
- 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 +0 -6
- package/src/internal/get.ts +17 -3
- 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 -207
- package/src/internal/store.ts +43 -16
- package/src/internal/subscribe-internal.ts +1 -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 +37 -156
- 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 +14 -146
- 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/space-states.ts +2 -2
- package/src/realtime/README.md +33 -0
- package/src/realtime/hook-composition/index.ts +1 -0
- package/src/realtime/hook-composition/receive-state.ts +29 -0
- package/src/realtime/hook-composition/receive-transaction.ts +2 -3
- 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/silo.ts +12 -4
- package/src/subscribe.ts +30 -2
- package/src/timeline.ts +10 -0
- package/src/transaction.ts +15 -10
- package/src/realtime-client/hook-composition/compose-realtime-hooks.ts +0 -62
- package/src/realtime-client/hook-composition/realtime-client-family-member.ts +0 -28
- package/src/realtime-client/hook-composition/realtime-client-family.ts +0 -26
- package/src/realtime-client/hook-composition/realtime-client-single.ts +0 -24
- package/src/realtime-client/hook-composition/realtime-client-transaction.ts +0 -35
- package/src/realtime-client/index.ts +0 -1
|
@@ -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`] = {
|
|
@@ -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"
|
|
@@ -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,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
|
+
}
|
|
@@ -8,9 +8,8 @@ export const useReceiveTransaction = ({ socket, store }: ServerConfig) => {
|
|
|
8
8
|
return function receiveTransaction<ƒ extends ƒn>(
|
|
9
9
|
tx: AtomIO.TransactionToken<ƒ>
|
|
10
10
|
): () => void {
|
|
11
|
-
const fillTransactionRequest = (
|
|
12
|
-
|
|
13
|
-
) => AtomIO.runTransaction<ƒ>(tx, store)(...update.params)
|
|
11
|
+
const fillTransactionRequest = (update: AtomIO.TransactionUpdate<ƒ>) =>
|
|
12
|
+
AtomIO.runTransaction<ƒ>(tx, store)(...update.params)
|
|
14
13
|
|
|
15
14
|
socket.on(`tx:${tx.key}`, fillTransactionRequest)
|
|
16
15
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AR from "atom.io/react"
|
|
4
|
+
import type { Socket } from "socket.io-client"
|
|
5
|
+
import { io } from "socket.io-client"
|
|
6
|
+
|
|
7
|
+
import { myIdState__INTERNAL } from "./realtime-state"
|
|
8
|
+
|
|
9
|
+
export const RealtimeContext = React.createContext<{ socket: Socket }>({
|
|
10
|
+
socket: io(),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const RealtimeProvider: React.FC<{
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
socket: Socket
|
|
16
|
+
}> = ({ children, socket }) => {
|
|
17
|
+
const setMyId = AR.useI(myIdState__INTERNAL)
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
socket.on(`connect`, () => {
|
|
20
|
+
setMyId(socket.id)
|
|
21
|
+
})
|
|
22
|
+
socket.on(`disconnect`, () => {
|
|
23
|
+
setMyId(null)
|
|
24
|
+
})
|
|
25
|
+
}, [socket, setMyId])
|
|
26
|
+
return (
|
|
27
|
+
<RealtimeContext.Provider value={{ socket }}>
|
|
28
|
+
{children}
|
|
29
|
+
</RealtimeContext.Provider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
|
|
3
|
+
import type { ƒn } from "~/packages/anvl/src/function"
|
|
4
|
+
import type { Json } from "~/packages/anvl/src/json"
|
|
5
|
+
|
|
6
|
+
import { usePull } from "./use-pull"
|
|
7
|
+
import { usePullFamily } from "./use-pull-family"
|
|
8
|
+
import { usePullFamilyMember } from "./use-pull-family-member"
|
|
9
|
+
import { usePush } from "./use-push"
|
|
10
|
+
import { useServerAction } from "./use-server-action"
|
|
11
|
+
|
|
12
|
+
export type RealtimeHooks = {
|
|
13
|
+
usePull: <J extends Json>(token: AtomIO.StateToken<J>) => void
|
|
14
|
+
usePullFamily: <J extends Json>(
|
|
15
|
+
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>
|
|
16
|
+
) => void
|
|
17
|
+
usePullFamilyMember: <J extends Json>(
|
|
18
|
+
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>,
|
|
19
|
+
subKey: string
|
|
20
|
+
) => void
|
|
21
|
+
usePush: <J extends Json>(token: AtomIO.StateToken<J>) => void
|
|
22
|
+
useServerAction: <ƒ extends ƒn>(
|
|
23
|
+
token: AtomIO.TransactionToken<ƒ>
|
|
24
|
+
) => (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const realtimeHooks: RealtimeHooks = {
|
|
28
|
+
usePull,
|
|
29
|
+
usePullFamily,
|
|
30
|
+
usePullFamilyMember,
|
|
31
|
+
usePush,
|
|
32
|
+
useServerAction,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export * from "./use-pull"
|
|
36
|
+
export * from "./use-pull-family"
|
|
37
|
+
export * from "./use-pull-family-member"
|
|
38
|
+
export * from "./use-push"
|
|
39
|
+
export * from "./use-server-action"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as AtomIO from "atom.io"
|
|
2
|
+
|
|
3
|
+
export const myIdState__INTERNAL = AtomIO.atom<string | null>({
|
|
4
|
+
key: `myId__INTERNAL`,
|
|
5
|
+
default: null,
|
|
6
|
+
})
|
|
7
|
+
export const myIdState = AtomIO.selector<string | null>({
|
|
8
|
+
key: `myId`,
|
|
9
|
+
get: ({ get }) => get(myIdState__INTERNAL),
|
|
10
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { Json } from "~/packages/anvl/src/json"
|
|
6
|
+
|
|
7
|
+
import { RealtimeContext } from "./realtime-context"
|
|
8
|
+
import { StoreContext } from "../react"
|
|
9
|
+
|
|
10
|
+
export function usePullFamilyMember<J extends Json>(
|
|
11
|
+
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>,
|
|
12
|
+
subKey: AtomIO.Serializable
|
|
13
|
+
): void {
|
|
14
|
+
const token = family(subKey)
|
|
15
|
+
const { socket } = React.useContext(RealtimeContext)
|
|
16
|
+
const store = React.useContext(StoreContext)
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
socket?.on(`serve:${token.key}`, (data: J) => {
|
|
19
|
+
AtomIO.setState(family(subKey), data, store)
|
|
20
|
+
})
|
|
21
|
+
socket?.emit(`sub:${family.key}`, subKey)
|
|
22
|
+
return () => {
|
|
23
|
+
socket?.off(`serve:${token.key}`)
|
|
24
|
+
socket?.emit(`unsub:${token.key}`)
|
|
25
|
+
}
|
|
26
|
+
}, [family.key])
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { Json } from "~/packages/anvl/src/json"
|
|
6
|
+
|
|
7
|
+
import { RealtimeContext } from "./realtime-context"
|
|
8
|
+
import { StoreContext } from "../react"
|
|
9
|
+
|
|
10
|
+
export function usePullFamily<J extends Json>(
|
|
11
|
+
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>
|
|
12
|
+
): void {
|
|
13
|
+
const { socket } = React.useContext(RealtimeContext)
|
|
14
|
+
const store = React.useContext(StoreContext)
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
socket.on(`serve:${family.key}`, (key: Json, data: J) => {
|
|
17
|
+
AtomIO.setState(family(key), data, store)
|
|
18
|
+
})
|
|
19
|
+
socket?.emit(`sub:${family.key}`)
|
|
20
|
+
return () => {
|
|
21
|
+
socket?.off(`serve:${family.key}`)
|
|
22
|
+
socket?.emit(`unsub:${family.key}`)
|
|
23
|
+
}
|
|
24
|
+
}, [family.key])
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { Json } from "~/packages/anvl/src/json"
|
|
6
|
+
|
|
7
|
+
import { RealtimeContext } from "./realtime-context"
|
|
8
|
+
import { StoreContext } from "../react"
|
|
9
|
+
|
|
10
|
+
export function usePull<J extends Json>(token: AtomIO.StateToken<J>): void {
|
|
11
|
+
const { socket } = React.useContext(RealtimeContext)
|
|
12
|
+
const store = React.useContext(StoreContext)
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
socket.on(`serve:${token.key}`, (data: J) => {
|
|
15
|
+
AtomIO.setState(token, data, store)
|
|
16
|
+
})
|
|
17
|
+
socket.emit(`sub:${token.key}`)
|
|
18
|
+
return () => {
|
|
19
|
+
socket.off(`serve:${token.key}`)
|
|
20
|
+
socket.emit(`unsub:${token.key}`)
|
|
21
|
+
}
|
|
22
|
+
}, [token.key])
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
|
|
5
|
+
import type { Json } from "~/packages/anvl/src/json"
|
|
6
|
+
|
|
7
|
+
import { RealtimeContext } from "./realtime-context"
|
|
8
|
+
import { StoreContext } from "../react"
|
|
9
|
+
|
|
10
|
+
export function usePush<J extends Json>(token: AtomIO.StateToken<J>): void {
|
|
11
|
+
const { socket } = React.useContext(RealtimeContext)
|
|
12
|
+
const store = React.useContext(StoreContext)
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
socket.emit(`claim:${token.key}`)
|
|
15
|
+
AtomIO.subscribe(
|
|
16
|
+
token,
|
|
17
|
+
({ newValue }) => {
|
|
18
|
+
socket.emit(`pub:${token.key}`, newValue)
|
|
19
|
+
},
|
|
20
|
+
store
|
|
21
|
+
)
|
|
22
|
+
return () => {
|
|
23
|
+
socket.emit(`unclaim:${token.key}`)
|
|
24
|
+
}
|
|
25
|
+
}, [token.key])
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import * as AtomIO from "atom.io"
|
|
4
|
+
import { StoreContext } from "atom.io/react"
|
|
5
|
+
|
|
6
|
+
import type { ƒn } from "~/packages/anvl/src/function"
|
|
7
|
+
|
|
8
|
+
import { RealtimeContext } from "./realtime-context"
|
|
9
|
+
|
|
10
|
+
const TX_SUBS = new Map<string, number>()
|
|
11
|
+
export function useServerAction<ƒ extends ƒn>(
|
|
12
|
+
token: AtomIO.TransactionToken<ƒ>
|
|
13
|
+
): (...parameters: Parameters<ƒ>) => ReturnType<ƒ> {
|
|
14
|
+
const store = React.useContext(StoreContext)
|
|
15
|
+
const { socket } = React.useContext(RealtimeContext)
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
const count = TX_SUBS.get(token.key) ?? 0
|
|
18
|
+
TX_SUBS.set(token.key, count + 1)
|
|
19
|
+
const unsubscribe =
|
|
20
|
+
count === 0
|
|
21
|
+
? AtomIO.subscribeToTransaction(
|
|
22
|
+
token,
|
|
23
|
+
(update) => socket.emit(`tx:${token.key}`, update),
|
|
24
|
+
store
|
|
25
|
+
)
|
|
26
|
+
: () => null
|
|
27
|
+
return () => {
|
|
28
|
+
const newCount = TX_SUBS.get(token.key) ?? 0
|
|
29
|
+
TX_SUBS.set(token.key, newCount - 1)
|
|
30
|
+
unsubscribe()
|
|
31
|
+
}
|
|
32
|
+
}, [token.key])
|
|
33
|
+
return AtomIO.runTransaction(token, store)
|
|
34
|
+
}
|
package/src/silo.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { redo, timeline, undo } from "."
|
|
2
|
+
import { getState, setState, subscribe } from "."
|
|
2
3
|
import type { atom, atomFamily } from "./atom"
|
|
4
|
+
import type { Store } from "./internal"
|
|
3
5
|
import {
|
|
4
|
-
type Store,
|
|
5
6
|
atomFamily__INTERNAL,
|
|
6
7
|
atom__INTERNAL,
|
|
7
8
|
createStore,
|
|
9
|
+
redo__INTERNAL,
|
|
8
10
|
selectorFamily__INTERNAL,
|
|
9
11
|
selector__INTERNAL,
|
|
10
12
|
timeline__INTERNAL,
|
|
11
13
|
transaction__INTERNAL,
|
|
14
|
+
undo__INTERNAL,
|
|
12
15
|
} from "./internal"
|
|
13
16
|
import type { selector, selectorFamily } from "./selector"
|
|
14
17
|
import type { transaction } from "./transaction"
|
|
@@ -16,7 +19,8 @@ import type { transaction } from "./transaction"
|
|
|
16
19
|
export type Silo = ReturnType<typeof silo>
|
|
17
20
|
|
|
18
21
|
export const silo = (
|
|
19
|
-
name: string
|
|
22
|
+
name: string,
|
|
23
|
+
fromStore: Store | null = null
|
|
20
24
|
): {
|
|
21
25
|
store: Store
|
|
22
26
|
atom: typeof atom
|
|
@@ -28,8 +32,10 @@ export const silo = (
|
|
|
28
32
|
getState: typeof getState
|
|
29
33
|
setState: typeof setState
|
|
30
34
|
subscribe: typeof subscribe
|
|
35
|
+
undo: typeof undo
|
|
36
|
+
redo: typeof redo
|
|
31
37
|
} => {
|
|
32
|
-
const store = createStore(name)
|
|
38
|
+
const store = createStore(name, fromStore)
|
|
33
39
|
return {
|
|
34
40
|
store,
|
|
35
41
|
atom: (options) => atom__INTERNAL(options, undefined, store),
|
|
@@ -41,5 +47,7 @@ export const silo = (
|
|
|
41
47
|
getState: (token) => getState(token, store),
|
|
42
48
|
setState: (token, newValue) => setState(token, newValue, store),
|
|
43
49
|
subscribe: (token, handler) => subscribe(token, handler, store),
|
|
50
|
+
undo: (token) => undo__INTERNAL(token, store),
|
|
51
|
+
redo: (token) => redo__INTERNAL(token, store),
|
|
44
52
|
}
|
|
45
53
|
}
|
package/src/subscribe.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { ƒn } from "~/packages/anvl/src/function"
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
ReadonlySelectorToken,
|
|
5
|
+
StateToken,
|
|
6
|
+
TimelineToken,
|
|
7
|
+
TimelineUpdate,
|
|
8
|
+
TransactionToken,
|
|
9
|
+
TransactionUpdate,
|
|
10
|
+
} from "."
|
|
11
|
+
import type { Store } from "./internal"
|
|
5
12
|
import { IMPLICIT, subscribeToRootAtoms, withdraw } from "./internal"
|
|
6
13
|
|
|
7
14
|
export type StateUpdate<T> = { newValue: T; oldValue: T }
|
|
15
|
+
export type KeyedStateUpdate<T> = StateUpdate<T> & { key: string }
|
|
8
16
|
export type UpdateHandler<T> = (update: StateUpdate<T>) => void
|
|
9
17
|
|
|
10
18
|
export const subscribe = <T>(
|
|
@@ -65,3 +73,23 @@ export const subscribeToTransaction = <ƒ extends ƒn>(
|
|
|
65
73
|
}
|
|
66
74
|
return unsubscribe
|
|
67
75
|
}
|
|
76
|
+
|
|
77
|
+
export const subscribeToTimeline = (
|
|
78
|
+
token: TimelineToken,
|
|
79
|
+
handleUpdate: (update: TimelineUpdate) => void,
|
|
80
|
+
store = IMPLICIT.STORE
|
|
81
|
+
): (() => void) => {
|
|
82
|
+
const tl = withdraw(token, store)
|
|
83
|
+
if (tl === null) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Cannot subscribe to timeline "${token.key}": timeline not found in store "${store.config.name}".`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
store.config.logger?.info(`👀 subscribe to timeline "${token.key}"`)
|
|
89
|
+
const subscription = tl.subject.subscribe(handleUpdate)
|
|
90
|
+
const unsubscribe = () => {
|
|
91
|
+
store.config.logger?.info(`🙈 unsubscribe from timeline "${token.key}"`)
|
|
92
|
+
subscription.unsubscribe()
|
|
93
|
+
}
|
|
94
|
+
return unsubscribe
|
|
95
|
+
}
|
package/src/timeline.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { AtomFamily, AtomToken } from "."
|
|
2
|
+
import type {
|
|
3
|
+
TimelineAtomUpdate,
|
|
4
|
+
TimelineSelectorUpdate,
|
|
5
|
+
TimelineTransactionUpdate,
|
|
6
|
+
} from "./internal"
|
|
2
7
|
import { IMPLICIT } from "./internal"
|
|
3
8
|
import { redo__INTERNAL, timeline__INTERNAL, undo__INTERNAL } from "./internal/"
|
|
4
9
|
|
|
@@ -12,6 +17,11 @@ export type TimelineOptions = {
|
|
|
12
17
|
atoms: (AtomFamily<any> | AtomToken<any>)[]
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
export type TimelineUpdate =
|
|
21
|
+
| TimelineAtomUpdate
|
|
22
|
+
| TimelineSelectorUpdate
|
|
23
|
+
| TimelineTransactionUpdate
|
|
24
|
+
|
|
15
25
|
export const timeline = (options: TimelineOptions): TimelineToken => {
|
|
16
26
|
return timeline__INTERNAL(options)
|
|
17
27
|
}
|
package/src/transaction.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import type * as Rx from "rxjs"
|
|
2
|
-
|
|
3
1
|
import type { ƒn } from "~/packages/anvl/src/function"
|
|
4
2
|
|
|
5
|
-
import type { ReadonlySelectorToken, StateToken
|
|
6
|
-
import type { Store
|
|
3
|
+
import type { KeyedStateUpdate, ReadonlySelectorToken, StateToken } from "."
|
|
4
|
+
import type { Store } from "./internal"
|
|
7
5
|
import { IMPLICIT, transaction__INTERNAL, withdraw } from "./internal"
|
|
8
6
|
|
|
7
|
+
export type TransactionToken<_> = {
|
|
8
|
+
key: string
|
|
9
|
+
type: `transaction`
|
|
10
|
+
__brand?: _
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type TransactionUpdate<ƒ extends ƒn> = {
|
|
14
|
+
key: string
|
|
15
|
+
atomUpdates: KeyedStateUpdate<unknown>[]
|
|
16
|
+
params: Parameters<ƒ>
|
|
17
|
+
output: ReturnType<ƒ>
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
export type Transactors = {
|
|
10
21
|
get: <S>(state: ReadonlySelectorToken<S> | StateToken<S>) => S
|
|
11
22
|
set: <S>(state: StateToken<S>, newValue: S | ((oldValue: S) => S)) => void
|
|
@@ -27,12 +38,6 @@ export type TransactionOptions<ƒ extends ƒn> = {
|
|
|
27
38
|
do: Write<ƒ>
|
|
28
39
|
}
|
|
29
40
|
|
|
30
|
-
export type Transaction<ƒ extends ƒn> = {
|
|
31
|
-
key: string
|
|
32
|
-
type: `transaction`
|
|
33
|
-
run: (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
|
|
34
|
-
subject: Rx.Subject<TransactionUpdate<ƒ>>
|
|
35
|
-
}
|
|
36
41
|
export type TransactionIO<Token extends TransactionToken<any>> =
|
|
37
42
|
Token extends TransactionToken<infer ƒ> ? ƒ : never
|
|
38
43
|
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { atom, selector } from "atom.io"
|
|
2
|
-
import * as AtomIO from "atom.io"
|
|
3
|
-
import type * as SocketIO from "socket.io-client"
|
|
4
|
-
|
|
5
|
-
import type { ƒn } from "~/packages/anvl/src/function"
|
|
6
|
-
import type { Json } from "~/packages/anvl/src/json"
|
|
7
|
-
|
|
8
|
-
import { realtimeClientFamilyHook } from "./realtime-client-family"
|
|
9
|
-
import { realtimeClientFamilyMemberHook } from "./realtime-client-family-member"
|
|
10
|
-
import { realtimeClientSingleHook } from "./realtime-client-single"
|
|
11
|
-
import { realtimeClientTransactionHook } from "./realtime-client-transaction"
|
|
12
|
-
import { atom__INTERNAL, selector__INTERNAL } from "../../internal"
|
|
13
|
-
|
|
14
|
-
export type RealtimeClientHooks = {
|
|
15
|
-
socketIdState: AtomIO.ReadonlySelectorToken<string | null>
|
|
16
|
-
useRemoteState: <J extends Json>(token: AtomIO.StateToken<J>) => void
|
|
17
|
-
useRemoteFamily: <J extends Json>(
|
|
18
|
-
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>
|
|
19
|
-
) => void
|
|
20
|
-
useRemoteFamilyMember: <J extends Json>(
|
|
21
|
-
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>,
|
|
22
|
-
subKey: string
|
|
23
|
-
) => void
|
|
24
|
-
useRemoteTransaction: <ƒ extends ƒn>(
|
|
25
|
-
token: AtomIO.TransactionToken<ƒ>
|
|
26
|
-
) => (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const composeRealtimeHooks = (
|
|
30
|
-
socket: SocketIO.Socket,
|
|
31
|
-
store: AtomIO.Store = AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
32
|
-
): RealtimeClientHooks => {
|
|
33
|
-
const socketIdState_INTERNAL = atom__INTERNAL<string | null>(
|
|
34
|
-
{
|
|
35
|
-
key: `socketIdState_INTERNAL`,
|
|
36
|
-
default: null,
|
|
37
|
-
effects: [
|
|
38
|
-
({ setSelf }) => {
|
|
39
|
-
socket.on(`connection`, () => {
|
|
40
|
-
setSelf(socket.id)
|
|
41
|
-
})
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
undefined,
|
|
46
|
-
store
|
|
47
|
-
)
|
|
48
|
-
return {
|
|
49
|
-
socketIdState: selector__INTERNAL<string | null>(
|
|
50
|
-
{
|
|
51
|
-
key: `socketIdState`,
|
|
52
|
-
get: ({ get }) => get(socketIdState_INTERNAL),
|
|
53
|
-
},
|
|
54
|
-
undefined,
|
|
55
|
-
store
|
|
56
|
-
),
|
|
57
|
-
useRemoteState: realtimeClientSingleHook(socket, store),
|
|
58
|
-
useRemoteFamily: realtimeClientFamilyHook(socket, store),
|
|
59
|
-
useRemoteFamilyMember: realtimeClientFamilyMemberHook(socket, store),
|
|
60
|
-
useRemoteTransaction: realtimeClientTransactionHook(socket, store),
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react"
|
|
2
|
-
|
|
3
|
-
import * as AtomIO from "atom.io"
|
|
4
|
-
import type * as SocketIO from "socket.io-client"
|
|
5
|
-
|
|
6
|
-
import type { Json } from "~/packages/anvl/src/json"
|
|
7
|
-
|
|
8
|
-
export const realtimeClientFamilyMemberHook =
|
|
9
|
-
(
|
|
10
|
-
socket: SocketIO.Socket,
|
|
11
|
-
store: AtomIO.Store = AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
12
|
-
) =>
|
|
13
|
-
<J extends Json>(
|
|
14
|
-
family: AtomIO.AtomFamily<J> | AtomIO.SelectorFamily<J>,
|
|
15
|
-
subKey: AtomIO.Serializable
|
|
16
|
-
): void => {
|
|
17
|
-
const token = family(subKey)
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
socket.on(`serve:${token.key}`, (data: J) => {
|
|
20
|
-
AtomIO.setState(family(subKey), data, store)
|
|
21
|
-
})
|
|
22
|
-
socket.emit(`sub:${family.key}`, subKey)
|
|
23
|
-
return () => {
|
|
24
|
-
socket.off(`serve:${token.key}`)
|
|
25
|
-
socket.emit(`unsub:${token.key}`)
|
|
26
|
-
}
|
|
27
|
-
}, [family.key])
|
|
28
|
-
}
|