atom.io 0.4.0 → 0.5.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 +598 -0
- package/dist/index.d.ts +52 -14
- package/dist/index.js +143 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +142 -28
- 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 +35 -8
- package/react/dist/index.d.mts +17 -0
- package/react-devtools/dist/index.d.mts +15 -0
- package/react-devtools/dist/index.js +13 -10
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/dist/index.mjs +10 -7
- package/react-devtools/dist/index.mjs.map +1 -1
- package/realtime/dist/index.d.mts +25 -0
- package/realtime/dist/index.d.ts +25 -0
- package/realtime/dist/index.js +168 -0
- package/realtime/dist/index.js.map +1 -0
- package/realtime/dist/index.mjs +130 -0
- package/realtime/dist/index.mjs.map +1 -0
- package/realtime/package.json +15 -0
- package/src/index.ts +22 -0
- package/src/internal/atom-internal.ts +1 -1
- package/src/internal/families-internal.ts +3 -3
- package/src/internal/get.ts +22 -12
- package/src/internal/selector-internal.ts +10 -0
- package/src/internal/set.ts +1 -1
- package/src/internal/store.ts +1 -1
- package/src/internal/subscribe-internal.ts +5 -0
- package/src/internal/timeline-internal.ts +13 -1
- package/src/internal/transaction-internal.ts +24 -9
- package/src/json/index.ts +1 -0
- package/src/json/select-json.ts +18 -0
- package/src/react-devtools/TokenList.tsx +13 -5
- package/src/react-explorer/explorer-states.ts +5 -5
- package/src/react-explorer/index.ts +1 -1
- package/src/react-explorer/space-states.ts +6 -7
- 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 +11 -0
- package/src/realtime/hook-composition/receive-transaction.ts +19 -0
- package/src/realtime/index.ts +1 -0
- package/src/realtime-client/hook-composition/compose-realtime-hooks.ts +62 -0
- package/src/realtime-client/hook-composition/realtime-client-family-member.ts +28 -0
- package/src/realtime-client/hook-composition/realtime-client-family.ts +26 -0
- package/src/realtime-client/hook-composition/realtime-client-single.ts +24 -0
- package/src/realtime-client/hook-composition/realtime-client-transaction.ts +35 -0
- package/src/realtime-client/index.ts +1 -0
- package/src/selector.ts +9 -6
- package/src/silo.ts +45 -0
- package/src/subscribe.ts +13 -1
- package/src/transaction.ts +13 -4
|
@@ -59,6 +59,11 @@ export const subscribeToRootAtoms = <T>(
|
|
|
59
59
|
? null
|
|
60
60
|
: traceAllSelectorAtoms(state.key, store).map((atomToken) => {
|
|
61
61
|
const atom = withdraw(atomToken, store)
|
|
62
|
+
if (atom === null) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Atom "${atomToken.key}", a dependency of selector "${state.key}", not found in store "${store.config.name}".`
|
|
65
|
+
)
|
|
66
|
+
}
|
|
62
67
|
return atom.subject.subscribe((atomChange) => {
|
|
63
68
|
store.config.logger?.info(
|
|
64
69
|
`📢 selector "${state.key}" saw root "${atomToken.key}" go (`,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import HAMT from "hamt_plus"
|
|
2
2
|
|
|
3
|
+
import type { ƒn } from "~/packages/anvl/src/function"
|
|
4
|
+
|
|
3
5
|
import type { KeyedStateUpdate, TransactionUpdate, Store } from "."
|
|
4
6
|
import { target, IMPLICIT, withdraw } from "."
|
|
5
|
-
import type { AtomToken, TimelineOptions, TimelineToken
|
|
7
|
+
import type { AtomToken, TimelineOptions, TimelineToken } from ".."
|
|
6
8
|
|
|
7
9
|
export type Timeline = {
|
|
8
10
|
key: string
|
|
@@ -49,6 +51,11 @@ export function timeline__INTERNAL(
|
|
|
49
51
|
|
|
50
52
|
const subscribeToAtom = (token: AtomToken<any>) => {
|
|
51
53
|
const state = withdraw(token, store)
|
|
54
|
+
if (state === null) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Cannot subscribe to atom "${token.key}" because it has not been initialized in store "${store.config.name}"`
|
|
57
|
+
)
|
|
58
|
+
}
|
|
52
59
|
state.subject.subscribe((update) => {
|
|
53
60
|
const storeCurrentSelectorKey =
|
|
54
61
|
store.operation.open && store.operation.token.type === `selector`
|
|
@@ -83,6 +90,11 @@ export function timeline__INTERNAL(
|
|
|
83
90
|
{ key: storeCurrentTransactionKey, type: `transaction` },
|
|
84
91
|
store
|
|
85
92
|
)
|
|
93
|
+
if (currentTransaction === null) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Transaction "${storeCurrentTransactionKey}" not found in store "${store.config.name}". This is surprising, because we are in the application phase of "${storeCurrentTransactionKey}".`
|
|
96
|
+
)
|
|
97
|
+
}
|
|
86
98
|
if (timelineData.transactionKey !== storeCurrentTransactionKey) {
|
|
87
99
|
if (timelineData.transactionKey) {
|
|
88
100
|
store.config.logger?.error(
|
|
@@ -1,8 +1,10 @@
|
|
|
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 { deposit, withdraw, IMPLICIT } from "."
|
|
6
8
|
import { getState, setState } from ".."
|
|
7
9
|
import type {
|
|
8
10
|
AtomToken,
|
|
@@ -10,7 +12,6 @@ import type {
|
|
|
10
12
|
Transaction,
|
|
11
13
|
TransactionOptions,
|
|
12
14
|
TransactionToken,
|
|
13
|
-
ƒn,
|
|
14
15
|
} from ".."
|
|
15
16
|
|
|
16
17
|
export const TRANSACTION_PHASES = [`idle`, `building`, `applying`] as const
|
|
@@ -84,19 +85,22 @@ export const applyTransaction = <ƒ extends ƒn>(
|
|
|
84
85
|
for (const { key, newValue } of atomUpdates) {
|
|
85
86
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
86
87
|
if (!HAMT.has(token.key, store.valueMap)) {
|
|
87
|
-
const
|
|
88
|
-
store.atoms = HAMT.set(
|
|
89
|
-
store.valueMap = HAMT.set(
|
|
90
|
-
store.config.logger?.info(`🔧`, `add atom "${
|
|
88
|
+
const newAtom = HAMT.get(token.key, store.transactionStatus.core.atoms)
|
|
89
|
+
store.atoms = HAMT.set(newAtom.key, newAtom, store.atoms)
|
|
90
|
+
store.valueMap = HAMT.set(newAtom.key, newAtom.default, store.valueMap)
|
|
91
|
+
store.config.logger?.info(`🔧`, `add atom "${newAtom.key}"`)
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
setState(state, newValue, store)
|
|
93
|
+
setState(token, newValue, store)
|
|
95
94
|
}
|
|
96
95
|
const myTransaction = withdraw<ƒ>(
|
|
97
96
|
{ key: store.transactionStatus.key, type: `transaction` },
|
|
98
97
|
store
|
|
99
98
|
)
|
|
99
|
+
if (myTransaction === null) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Transaction "${store.transactionStatus.key}" not found. Absurd. How is this running?`
|
|
102
|
+
)
|
|
103
|
+
}
|
|
100
104
|
myTransaction.subject.next({
|
|
101
105
|
key: store.transactionStatus.key,
|
|
102
106
|
atomUpdates,
|
|
@@ -114,6 +118,12 @@ export const undoTransactionUpdate = <ƒ extends ƒn>(
|
|
|
114
118
|
for (const { key, oldValue } of update.atomUpdates) {
|
|
115
119
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
116
120
|
const state = withdraw(token, store)
|
|
121
|
+
if (state === null) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`State "${token.key}" not found in this store. This is surprising, because we are navigating the history of the store.`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
117
127
|
setState(state, oldValue, store)
|
|
118
128
|
}
|
|
119
129
|
}
|
|
@@ -125,6 +135,11 @@ export const redoTransactionUpdate = <ƒ extends ƒn>(
|
|
|
125
135
|
for (const { key, newValue } of update.atomUpdates) {
|
|
126
136
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
127
137
|
const state = withdraw(token, store)
|
|
138
|
+
if (state === null) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`State "${token.key}" not found in this store. This is surprising, because we are navigating the history of the store.`
|
|
141
|
+
)
|
|
142
|
+
}
|
|
128
143
|
setState(state, newValue, store)
|
|
129
144
|
}
|
|
130
145
|
}
|
|
@@ -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
|
+
)
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
SelectorToken,
|
|
8
8
|
__INTERNAL__,
|
|
9
9
|
} from "atom.io"
|
|
10
|
+
import { getState } from "atom.io"
|
|
10
11
|
import type { StoreHooks } from "atom.io/react"
|
|
11
12
|
|
|
12
13
|
import { recordToEntries } from "~/packages/anvl/src/object"
|
|
@@ -30,14 +31,21 @@ export const TokenList: FC<{
|
|
|
30
31
|
<Fragment key={key}>
|
|
31
32
|
{key.startsWith(`👁🗨_`) ? null : (
|
|
32
33
|
<div className="node">
|
|
33
|
-
{key}:
|
|
34
34
|
{`type` in token ? (
|
|
35
|
-
|
|
35
|
+
<>
|
|
36
|
+
<label onClick={() => console.log(token, getState(token))}>
|
|
37
|
+
{key}
|
|
38
|
+
</label>
|
|
39
|
+
<StoreEditor storeHooks={storeHooks} token={token} />
|
|
40
|
+
</>
|
|
36
41
|
) : (
|
|
37
42
|
recordToEntries(token.familyMembers).map(([key, token]) => (
|
|
38
|
-
|
|
39
|
-
{key}
|
|
40
|
-
|
|
43
|
+
<>
|
|
44
|
+
<label>{key}</label>
|
|
45
|
+
<div key={key} className="node">
|
|
46
|
+
{key}:<StoreEditor storeHooks={storeHooks} token={token} />
|
|
47
|
+
</div>
|
|
48
|
+
</>
|
|
41
49
|
))
|
|
42
50
|
)}
|
|
43
51
|
</div>
|
|
@@ -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`,
|
|
@@ -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,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,11 @@
|
|
|
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
|
+
|
|
8
|
+
export type ServerConfig = {
|
|
9
|
+
socket: SocketIO.Socket
|
|
10
|
+
store?: AtomIO.__INTERNAL__.Store
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 = (
|
|
12
|
+
update: AtomIO.__INTERNAL__.TransactionUpdate<ƒ>
|
|
13
|
+
) => AtomIO.runTransaction<ƒ>(tx, store)(...update.params)
|
|
14
|
+
|
|
15
|
+
socket.on(`tx:${tx.key}`, fillTransactionRequest)
|
|
16
|
+
|
|
17
|
+
return () => socket.off(`tx:${tx.key}`, fillTransactionRequest)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./hook-composition"
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 realtimeClientFamilyHook =
|
|
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
|
+
): void => {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
socket.on(`serve:${family.key}`, (key: Json, data: J) => {
|
|
18
|
+
AtomIO.setState(family(key), data, store)
|
|
19
|
+
})
|
|
20
|
+
socket.emit(`sub:${family.key}`)
|
|
21
|
+
return () => {
|
|
22
|
+
socket.off(`serve:${family.key}`)
|
|
23
|
+
socket.emit(`unsub:${family.key}`)
|
|
24
|
+
}
|
|
25
|
+
}, [family.key])
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React 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 realtimeClientSingleHook =
|
|
9
|
+
(
|
|
10
|
+
socket: SocketIO.Socket,
|
|
11
|
+
store: AtomIO.Store = AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
12
|
+
) =>
|
|
13
|
+
<J extends Json>(token: AtomIO.StateToken<J>): void => {
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
socket.on(`serve:${token.key}`, (data: J) => {
|
|
16
|
+
AtomIO.setState(token, data, store)
|
|
17
|
+
})
|
|
18
|
+
socket.emit(`sub:${token.key}`)
|
|
19
|
+
return () => {
|
|
20
|
+
socket.off(`serve:${token.key}`)
|
|
21
|
+
socket.emit(`unsub:${token.key}`)
|
|
22
|
+
}
|
|
23
|
+
}, [token.key])
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { ƒn } from "~/packages/anvl/src/function"
|
|
7
|
+
|
|
8
|
+
const TX_SUBS = new Map<string, number>()
|
|
9
|
+
export const realtimeClientTransactionHook =
|
|
10
|
+
(
|
|
11
|
+
socket: SocketIO.Socket,
|
|
12
|
+
store: AtomIO.Store = AtomIO.__INTERNAL__.IMPLICIT.STORE
|
|
13
|
+
) =>
|
|
14
|
+
<ƒ extends ƒn>(
|
|
15
|
+
token: AtomIO.TransactionToken<ƒ>
|
|
16
|
+
): ((...parameters: Parameters<ƒ>) => ReturnType<ƒ>) => {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const count = TX_SUBS.get(token.key) ?? 0
|
|
19
|
+
TX_SUBS.set(token.key, count + 1)
|
|
20
|
+
const unsubscribe =
|
|
21
|
+
count === 0
|
|
22
|
+
? AtomIO.subscribeToTransaction(
|
|
23
|
+
token,
|
|
24
|
+
(update) => socket.emit(`tx:${token.key}`, update),
|
|
25
|
+
store
|
|
26
|
+
)
|
|
27
|
+
: () => null
|
|
28
|
+
return () => {
|
|
29
|
+
const newCount = TX_SUBS.get(token.key) ?? 0
|
|
30
|
+
TX_SUBS.set(token.key, newCount - 1)
|
|
31
|
+
unsubscribe()
|
|
32
|
+
}
|
|
33
|
+
}, [token.key])
|
|
34
|
+
return AtomIO.runTransaction(token, store)
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./hook-composition/compose-realtime-hooks"
|
package/src/selector.ts
CHANGED
|
@@ -11,12 +11,15 @@ export type SelectorOptions<T> = {
|
|
|
11
11
|
get: Read<() => T>
|
|
12
12
|
set: Write<(newValue: T) => void>
|
|
13
13
|
}
|
|
14
|
-
export type ReadonlySelectorOptions<T> =
|
|
14
|
+
export type ReadonlySelectorOptions<T> = {
|
|
15
|
+
key: string
|
|
16
|
+
get: Read<() => T>
|
|
17
|
+
}
|
|
15
18
|
|
|
19
|
+
export function selector<T>(options: SelectorOptions<T>): SelectorToken<T>
|
|
16
20
|
export function selector<T>(
|
|
17
21
|
options: ReadonlySelectorOptions<T>
|
|
18
22
|
): ReadonlySelectorToken<T>
|
|
19
|
-
export function selector<T>(options: SelectorOptions<T>): SelectorToken<T>
|
|
20
23
|
export function selector<T>(
|
|
21
24
|
options: ReadonlySelectorOptions<T> | SelectorOptions<T>
|
|
22
25
|
): ReadonlySelectorToken<T> | SelectorToken<T> {
|
|
@@ -28,10 +31,10 @@ export type SelectorFamilyOptions<T, K extends Serializable> = {
|
|
|
28
31
|
get: (key: K) => Read<() => T>
|
|
29
32
|
set: (key: K) => Write<(newValue: T) => void>
|
|
30
33
|
}
|
|
31
|
-
export type ReadonlySelectorFamilyOptions<T, K extends Serializable> =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
export type ReadonlySelectorFamilyOptions<T, K extends Serializable> = {
|
|
35
|
+
key: string
|
|
36
|
+
get: (key: K) => Read<() => T>
|
|
37
|
+
}
|
|
35
38
|
|
|
36
39
|
export type SelectorFamily<T, K extends Serializable = Serializable> = ((
|
|
37
40
|
key: K
|