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.
Files changed (101) hide show
  1. package/README.md +38 -10
  2. package/dist/index.d.mts +614 -0
  3. package/dist/index.d.ts +130 -77
  4. package/dist/index.js +584 -347
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +582 -347
  7. package/dist/index.mjs.map +1 -1
  8. package/json/dist/index.d.mts +18 -0
  9. package/json/dist/index.d.ts +18 -0
  10. package/json/dist/index.js +51 -0
  11. package/json/dist/index.js.map +1 -0
  12. package/json/dist/index.mjs +15 -0
  13. package/json/dist/index.mjs.map +1 -0
  14. package/json/package.json +15 -0
  15. package/package.json +43 -9
  16. package/react/dist/index.d.mts +24 -0
  17. package/react/dist/index.d.ts +18 -11
  18. package/react/dist/index.js +45 -21
  19. package/react/dist/index.js.map +1 -1
  20. package/react/dist/index.mjs +31 -21
  21. package/react/dist/index.mjs.map +1 -1
  22. package/react-devtools/dist/index.d.mts +15 -0
  23. package/react-devtools/dist/index.d.ts +4 -4
  24. package/react-devtools/dist/index.js +1 -1
  25. package/react-devtools/dist/index.js.map +1 -1
  26. package/react-devtools/dist/index.mjs +1 -1
  27. package/react-devtools/dist/index.mjs.map +1 -1
  28. package/realtime/dist/index.d.mts +27 -0
  29. package/realtime/dist/index.d.ts +27 -0
  30. package/realtime/dist/index.js +191 -0
  31. package/realtime/dist/index.js.map +1 -0
  32. package/realtime/dist/index.mjs +152 -0
  33. package/realtime/dist/index.mjs.map +1 -0
  34. package/realtime/package.json +15 -0
  35. package/realtime-react/dist/index.d.mts +45 -0
  36. package/realtime-react/dist/index.d.ts +45 -0
  37. package/realtime-react/dist/index.js +213 -0
  38. package/realtime-react/dist/index.js.map +1 -0
  39. package/realtime-react/dist/index.mjs +168 -0
  40. package/realtime-react/dist/index.mjs.map +1 -0
  41. package/realtime-react/package.json +15 -0
  42. package/src/index.ts +21 -5
  43. package/src/internal/atom-internal.ts +1 -1
  44. package/src/internal/families-internal.ts +3 -3
  45. package/src/internal/get.ts +39 -15
  46. package/src/internal/index.ts +2 -0
  47. package/src/internal/meta/meta-state.ts +1 -1
  48. package/src/internal/operation.ts +3 -1
  49. package/src/internal/selector/create-read-write-selector.ts +62 -0
  50. package/src/internal/selector/create-readonly-selector.ts +52 -0
  51. package/src/internal/selector/index.ts +4 -0
  52. package/src/internal/selector/lookup-selector-sources.ts +16 -0
  53. package/src/internal/selector/register-selector.ts +57 -0
  54. package/src/internal/selector/trace-selector-atoms.ts +43 -0
  55. package/src/internal/selector/update-selector-atoms.ts +33 -0
  56. package/src/internal/selector-internal.ts +9 -197
  57. package/src/internal/set.ts +1 -1
  58. package/src/internal/store.ts +44 -17
  59. package/src/internal/subscribe-internal.ts +6 -1
  60. package/src/internal/time-travel-internal.ts +7 -7
  61. package/src/internal/timeline/add-atom-to-timeline.ts +164 -0
  62. package/src/internal/timeline/index.ts +1 -0
  63. package/src/internal/timeline-internal.ts +39 -146
  64. package/src/internal/transaction/abort-transaction.ts +12 -0
  65. package/src/internal/transaction/apply-transaction.ts +54 -0
  66. package/src/internal/transaction/build-transaction.ts +33 -0
  67. package/src/internal/transaction/index.ts +25 -0
  68. package/src/internal/transaction/redo-transaction.ts +23 -0
  69. package/src/internal/transaction/undo-transaction.ts +23 -0
  70. package/src/internal/transaction-internal.ts +16 -133
  71. package/src/json/index.ts +1 -0
  72. package/src/json/select-json.ts +18 -0
  73. package/src/react/index.ts +2 -46
  74. package/src/react/store-context.tsx +14 -0
  75. package/src/react/store-hooks.ts +48 -0
  76. package/src/react-devtools/AtomIODevtools.tsx +1 -1
  77. package/src/react-explorer/AtomIOExplorer.tsx +2 -2
  78. package/src/react-explorer/explorer-states.ts +5 -5
  79. package/src/react-explorer/index.ts +1 -1
  80. package/src/react-explorer/space-states.ts +8 -9
  81. package/src/realtime/README.md +33 -0
  82. package/src/realtime/hook-composition/expose-family.ts +101 -0
  83. package/src/realtime/hook-composition/expose-single.ts +38 -0
  84. package/src/realtime/hook-composition/index.ts +12 -0
  85. package/src/realtime/hook-composition/receive-state.ts +29 -0
  86. package/src/realtime/hook-composition/receive-transaction.ts +18 -0
  87. package/src/realtime/index.ts +1 -0
  88. package/src/realtime-react/index.ts +3 -0
  89. package/src/realtime-react/realtime-context.tsx +31 -0
  90. package/src/realtime-react/realtime-hooks.ts +39 -0
  91. package/src/realtime-react/realtime-state.ts +10 -0
  92. package/src/realtime-react/use-pull-family-member.ts +27 -0
  93. package/src/realtime-react/use-pull-family.ts +25 -0
  94. package/src/realtime-react/use-pull.ts +23 -0
  95. package/src/realtime-react/use-push.ts +26 -0
  96. package/src/realtime-react/use-server-action.ts +34 -0
  97. package/src/selector.ts +9 -6
  98. package/src/silo.ts +53 -0
  99. package/src/subscribe.ts +42 -2
  100. package/src/timeline.ts +10 -0
  101. 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 { cacheValue, deposit, withdraw, IMPLICIT } from "."
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 KeyedStateUpdate<T> = StateUpdate<T> & {
20
- key: string
21
- }
22
- export type TransactionUpdate<ƒ extends ƒn> = {
17
+ export type Transaction<ƒ extends ƒn> = {
23
18
  key: string
24
- atomUpdates: KeyedStateUpdate<unknown>[]
25
- params: Parameters<ƒ>
26
- output: ReturnType<ƒ>
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
+ )
@@ -1,46 +1,2 @@
1
- import { useSyncExternalStore } from "react"
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: ReturnType<typeof composeStoreHooks>
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), cannotExist, `viewId`, `spaceId`),
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`,
@@ -1,4 +1,4 @@
1
- import type { AtomToken, Write } from ".."
1
+ import type { AtomToken, Write } from "atom.io"
2
2
 
3
3
  export * from "./AtomIOExplorer"
4
4
 
@@ -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"
@@ -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
- json,
35
- hasExactProperties({ size: isNumber }),
36
- `parent`,
37
- `child`
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"
@@ -0,0 +1,3 @@
1
+ export * from "./realtime-context"
2
+ export * from "./realtime-hooks"
3
+ export * from "./realtime-state"