atom.io 0.4.1 → 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.
Files changed (58) hide show
  1. package/README.md +38 -10
  2. package/dist/index.d.mts +598 -0
  3. package/dist/index.d.ts +51 -14
  4. package/dist/index.js +143 -28
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +142 -28
  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 +34 -7
  16. package/react/dist/index.d.mts +17 -0
  17. package/react-devtools/dist/index.d.mts +15 -0
  18. package/react-devtools/dist/index.js +1 -1
  19. package/react-devtools/dist/index.js.map +1 -1
  20. package/react-devtools/dist/index.mjs +1 -1
  21. package/react-devtools/dist/index.mjs.map +1 -1
  22. package/realtime/dist/index.d.mts +25 -0
  23. package/realtime/dist/index.d.ts +25 -0
  24. package/realtime/dist/index.js +168 -0
  25. package/realtime/dist/index.js.map +1 -0
  26. package/realtime/dist/index.mjs +130 -0
  27. package/realtime/dist/index.mjs.map +1 -0
  28. package/realtime/package.json +15 -0
  29. package/src/index.ts +22 -0
  30. package/src/internal/atom-internal.ts +1 -1
  31. package/src/internal/families-internal.ts +3 -3
  32. package/src/internal/get.ts +22 -12
  33. package/src/internal/selector-internal.ts +10 -0
  34. package/src/internal/set.ts +1 -1
  35. package/src/internal/store.ts +1 -1
  36. package/src/internal/subscribe-internal.ts +5 -0
  37. package/src/internal/timeline-internal.ts +13 -1
  38. package/src/internal/transaction-internal.ts +24 -9
  39. package/src/json/index.ts +1 -0
  40. package/src/json/select-json.ts +18 -0
  41. package/src/react-explorer/explorer-states.ts +5 -5
  42. package/src/react-explorer/index.ts +1 -1
  43. package/src/react-explorer/space-states.ts +6 -7
  44. package/src/realtime/hook-composition/expose-family.ts +101 -0
  45. package/src/realtime/hook-composition/expose-single.ts +38 -0
  46. package/src/realtime/hook-composition/index.ts +11 -0
  47. package/src/realtime/hook-composition/receive-transaction.ts +19 -0
  48. package/src/realtime/index.ts +1 -0
  49. package/src/realtime-client/hook-composition/compose-realtime-hooks.ts +62 -0
  50. package/src/realtime-client/hook-composition/realtime-client-family-member.ts +28 -0
  51. package/src/realtime-client/hook-composition/realtime-client-family.ts +26 -0
  52. package/src/realtime-client/hook-composition/realtime-client-single.ts +24 -0
  53. package/src/realtime-client/hook-composition/realtime-client-transaction.ts +35 -0
  54. package/src/realtime-client/index.ts +1 -0
  55. package/src/selector.ts +9 -6
  56. package/src/silo.ts +45 -0
  57. package/src/subscribe.ts +13 -1
  58. package/src/transaction.ts +11 -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, ƒn } from ".."
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 { cacheValue, deposit, withdraw, IMPLICIT } from "."
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 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}"`)
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
- const state = withdraw(token, store)
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
+ )
@@ -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
 
@@ -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,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> = Omit<SelectorOptions<T>, `set`>
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> = Omit<
32
- SelectorFamilyOptions<T, K>,
33
- `set`
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
package/src/silo.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { type timeline, getState, setState, subscribe } from "."
2
+ import type { atom, atomFamily } from "./atom"
3
+ import {
4
+ type Store,
5
+ atomFamily__INTERNAL,
6
+ atom__INTERNAL,
7
+ createStore,
8
+ selectorFamily__INTERNAL,
9
+ selector__INTERNAL,
10
+ timeline__INTERNAL,
11
+ transaction__INTERNAL,
12
+ } from "./internal"
13
+ import type { selector, selectorFamily } from "./selector"
14
+ import type { transaction } from "./transaction"
15
+
16
+ export type Silo = ReturnType<typeof silo>
17
+
18
+ export const silo = (
19
+ name: string
20
+ ): {
21
+ store: Store
22
+ atom: typeof atom
23
+ atomFamily: typeof atomFamily
24
+ selector: typeof selector
25
+ selectorFamily: typeof selectorFamily
26
+ transaction: typeof transaction
27
+ timeline: typeof timeline
28
+ getState: typeof getState
29
+ setState: typeof setState
30
+ subscribe: typeof subscribe
31
+ } => {
32
+ const store = createStore(name)
33
+ return {
34
+ store,
35
+ atom: (options) => atom__INTERNAL(options, undefined, store),
36
+ atomFamily: (options) => atomFamily__INTERNAL(options, store),
37
+ selector: (options) => selector__INTERNAL(options, undefined, store) as any,
38
+ selectorFamily: (options) => selectorFamily__INTERNAL(options, store) as any,
39
+ transaction: (options) => transaction__INTERNAL(options, store),
40
+ timeline: (options) => timeline__INTERNAL(options, store),
41
+ getState: (token) => getState(token, store),
42
+ setState: (token, newValue) => setState(token, newValue, store),
43
+ subscribe: (token, handler) => subscribe(token, handler, store),
44
+ }
45
+ }