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.
Files changed (87) hide show
  1. package/dist/index.d.mts +82 -66
  2. package/dist/index.d.ts +82 -66
  3. package/dist/index.js +482 -360
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +481 -360
  6. package/dist/index.mjs.map +1 -1
  7. package/json/dist/index.js.map +1 -1
  8. package/json/dist/index.mjs.map +1 -1
  9. package/package.json +12 -5
  10. package/react/dist/index.d.mts +18 -11
  11. package/react/dist/index.d.ts +18 -11
  12. package/react/dist/index.js +45 -21
  13. package/react/dist/index.js.map +1 -1
  14. package/react/dist/index.mjs +31 -21
  15. package/react/dist/index.mjs.map +1 -1
  16. package/react-devtools/dist/index.d.mts +4 -4
  17. package/react-devtools/dist/index.d.ts +4 -4
  18. package/react-devtools/dist/index.js.map +1 -1
  19. package/react-devtools/dist/index.mjs.map +1 -1
  20. package/realtime/dist/index.d.mts +3 -1
  21. package/realtime/dist/index.d.ts +3 -1
  22. package/realtime/dist/index.js +23 -0
  23. package/realtime/dist/index.js.map +1 -1
  24. package/realtime/dist/index.mjs +22 -0
  25. package/realtime/dist/index.mjs.map +1 -1
  26. package/realtime-react/dist/index.d.mts +45 -0
  27. package/realtime-react/dist/index.d.ts +45 -0
  28. package/realtime-react/dist/index.js +213 -0
  29. package/realtime-react/dist/index.js.map +1 -0
  30. package/realtime-react/dist/index.mjs +168 -0
  31. package/realtime-react/dist/index.mjs.map +1 -0
  32. package/realtime-react/package.json +15 -0
  33. package/src/index.ts +0 -6
  34. package/src/internal/get.ts +17 -3
  35. package/src/internal/index.ts +2 -0
  36. package/src/internal/meta/meta-state.ts +1 -1
  37. package/src/internal/operation.ts +3 -1
  38. package/src/internal/selector/create-read-write-selector.ts +62 -0
  39. package/src/internal/selector/create-readonly-selector.ts +52 -0
  40. package/src/internal/selector/index.ts +4 -0
  41. package/src/internal/selector/lookup-selector-sources.ts +16 -0
  42. package/src/internal/selector/register-selector.ts +57 -0
  43. package/src/internal/selector/trace-selector-atoms.ts +43 -0
  44. package/src/internal/selector/update-selector-atoms.ts +33 -0
  45. package/src/internal/selector-internal.ts +9 -207
  46. package/src/internal/store.ts +43 -16
  47. package/src/internal/subscribe-internal.ts +1 -1
  48. package/src/internal/time-travel-internal.ts +7 -7
  49. package/src/internal/timeline/add-atom-to-timeline.ts +164 -0
  50. package/src/internal/timeline/index.ts +1 -0
  51. package/src/internal/timeline-internal.ts +37 -156
  52. package/src/internal/transaction/abort-transaction.ts +12 -0
  53. package/src/internal/transaction/apply-transaction.ts +54 -0
  54. package/src/internal/transaction/build-transaction.ts +33 -0
  55. package/src/internal/transaction/index.ts +25 -0
  56. package/src/internal/transaction/redo-transaction.ts +23 -0
  57. package/src/internal/transaction/undo-transaction.ts +23 -0
  58. package/src/internal/transaction-internal.ts +14 -146
  59. package/src/react/index.ts +2 -46
  60. package/src/react/store-context.tsx +14 -0
  61. package/src/react/store-hooks.ts +48 -0
  62. package/src/react-devtools/AtomIODevtools.tsx +1 -1
  63. package/src/react-explorer/AtomIOExplorer.tsx +2 -2
  64. package/src/react-explorer/space-states.ts +2 -2
  65. package/src/realtime/README.md +33 -0
  66. package/src/realtime/hook-composition/index.ts +1 -0
  67. package/src/realtime/hook-composition/receive-state.ts +29 -0
  68. package/src/realtime/hook-composition/receive-transaction.ts +2 -3
  69. package/src/realtime-react/index.ts +3 -0
  70. package/src/realtime-react/realtime-context.tsx +31 -0
  71. package/src/realtime-react/realtime-hooks.ts +39 -0
  72. package/src/realtime-react/realtime-state.ts +10 -0
  73. package/src/realtime-react/use-pull-family-member.ts +27 -0
  74. package/src/realtime-react/use-pull-family.ts +25 -0
  75. package/src/realtime-react/use-pull.ts +23 -0
  76. package/src/realtime-react/use-push.ts +26 -0
  77. package/src/realtime-react/use-server-action.ts +34 -0
  78. package/src/silo.ts +12 -4
  79. package/src/subscribe.ts +30 -2
  80. package/src/timeline.ts +10 -0
  81. package/src/transaction.ts +15 -10
  82. package/src/realtime-client/hook-composition/compose-realtime-hooks.ts +0 -62
  83. package/src/realtime-client/hook-composition/realtime-client-family-member.ts +0 -28
  84. package/src/realtime-client/hook-composition/realtime-client-family.ts +0 -26
  85. package/src/realtime-client/hook-composition/realtime-client-single.ts +0 -24
  86. package/src/realtime-client/hook-composition/realtime-client-transaction.ts +0 -35
  87. 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: ReturnType<typeof composeStoreHooks>
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, SelectorFamily } from ".."
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"
@@ -4,6 +4,7 @@ import type * as SocketIO from "socket.io"
4
4
  export * from "./expose-single"
5
5
  export * from "./expose-family"
6
6
  export * from "./receive-transaction"
7
+ export * from "./receive-state"
7
8
 
8
9
  export type ServerConfig = {
9
10
  socket: SocketIO.Socket
@@ -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
- update: AtomIO.__INTERNAL__.TransactionUpdate<ƒ>
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,3 @@
1
+ export * from "./realtime-context"
2
+ export * from "./realtime-hooks"
3
+ export * from "./realtime-state"
@@ -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 { type timeline, getState, setState, subscribe } from "."
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 { ReadonlySelectorToken, StateToken, TransactionToken } from "."
4
- import type { Store, TransactionUpdate } from "./internal"
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
  }
@@ -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, TransactionToken } from "."
6
- import type { Store, TransactionUpdate } from "./internal"
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
- }