atom.io 0.3.0 → 0.4.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 (55) hide show
  1. package/README.md +14 -8
  2. package/dist/index.d.ts +117 -62
  3. package/dist/index.js +577 -287
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +574 -285
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +16 -6
  8. package/react/dist/index.d.ts +12 -17
  9. package/react/dist/index.js +25 -34
  10. package/react/dist/index.js.map +1 -1
  11. package/react/dist/index.mjs +21 -34
  12. package/react/dist/index.mjs.map +1 -1
  13. package/react-devtools/dist/index.css +26 -0
  14. package/react-devtools/dist/index.css.map +1 -0
  15. package/react-devtools/dist/index.d.ts +15 -0
  16. package/react-devtools/dist/index.js +1579 -0
  17. package/react-devtools/dist/index.js.map +1 -0
  18. package/react-devtools/dist/index.mjs +1551 -0
  19. package/react-devtools/dist/index.mjs.map +1 -0
  20. package/react-devtools/package.json +15 -0
  21. package/src/index.ts +14 -8
  22. package/src/internal/atom-internal.ts +10 -5
  23. package/src/internal/families-internal.ts +7 -7
  24. package/src/internal/get.ts +9 -9
  25. package/src/internal/index.ts +2 -1
  26. package/src/internal/meta/attach-meta.ts +17 -0
  27. package/src/internal/meta/index.ts +4 -0
  28. package/src/internal/meta/meta-state.ts +135 -0
  29. package/src/internal/meta/meta-timelines.ts +1 -0
  30. package/src/internal/meta/meta-transactions.ts +1 -0
  31. package/src/internal/operation.ts +14 -3
  32. package/src/internal/selector-internal.ts +37 -15
  33. package/src/internal/store.ts +35 -6
  34. package/src/internal/time-travel-internal.ts +89 -0
  35. package/src/internal/timeline-internal.ts +110 -93
  36. package/src/internal/transaction-internal.ts +14 -5
  37. package/src/{internal/logger.ts → logger.ts} +2 -2
  38. package/src/react/index.ts +28 -46
  39. package/src/react-devtools/AtomIODevtools.tsx +107 -0
  40. package/src/react-devtools/StateEditor.tsx +73 -0
  41. package/src/react-devtools/TokenList.tsx +49 -0
  42. package/src/react-devtools/devtools.scss +130 -0
  43. package/src/react-devtools/index.ts +1 -0
  44. package/src/react-explorer/AtomIOExplorer.tsx +208 -0
  45. package/src/react-explorer/explorer-effects.ts +20 -0
  46. package/src/react-explorer/explorer-states.ts +224 -0
  47. package/src/react-explorer/index.ts +23 -0
  48. package/src/react-explorer/space-states.ts +73 -0
  49. package/src/react-explorer/view-states.ts +43 -0
  50. package/src/selector.ts +11 -11
  51. package/src/subscribe.ts +3 -3
  52. package/src/timeline.ts +3 -12
  53. package/src/transaction.ts +9 -4
  54. package/src/web-effects/index.ts +1 -0
  55. package/src/web-effects/storage.ts +30 -0
@@ -0,0 +1,135 @@
1
+ import type { AtomToken, ReadonlySelectorToken, SelectorToken } from "../.."
2
+ import { selector, atom } from "../.."
3
+ import type { Store } from "../store"
4
+ import { IMPLICIT } from "../store"
5
+
6
+ export type StateTokenIndex<
7
+ Token extends
8
+ | AtomToken<unknown>
9
+ | ReadonlySelectorToken<unknown>
10
+ | SelectorToken<unknown>
11
+ > = Record<
12
+ string,
13
+ | Token
14
+ | {
15
+ key: string
16
+ familyMembers: Record<string, Token>
17
+ }
18
+ >
19
+
20
+ export type AtomTokenIndex = StateTokenIndex<AtomToken<unknown>>
21
+ export type SelectorTokenIndex = StateTokenIndex<
22
+ ReadonlySelectorToken<unknown> | SelectorToken<unknown>
23
+ >
24
+
25
+ export const attachMetaAtoms = (
26
+ store: Store = IMPLICIT.STORE
27
+ ): ReadonlySelectorToken<AtomTokenIndex> => {
28
+ const atomTokenIndexState__INTERNAL = atom<AtomTokenIndex>({
29
+ key: `👁‍🗨_atom_token_index__INTERNAL`,
30
+ default: () =>
31
+ [...store.atoms].reduce<AtomTokenIndex>((acc, [key]) => {
32
+ acc[key] = { key, type: `atom` }
33
+ return acc
34
+ }, {}),
35
+ effects: [
36
+ ({ setSelf }) => {
37
+ store.subject.atomCreation.subscribe((atomToken) => {
38
+ if (store.operation.open) {
39
+ return
40
+ }
41
+ setSelf((state) => {
42
+ const { key, family } = atomToken
43
+ if (family) {
44
+ const { key: familyKey, subKey } = family
45
+ const current = state[familyKey]
46
+ if (current === undefined || `familyMembers` in current) {
47
+ const familyKeyState = current || {
48
+ key: familyKey,
49
+ familyMembers: {},
50
+ }
51
+ return {
52
+ ...state,
53
+ [familyKey]: {
54
+ ...familyKeyState,
55
+ familyMembers: {
56
+ ...familyKeyState.familyMembers,
57
+ [subKey]: atomToken,
58
+ },
59
+ },
60
+ }
61
+ }
62
+ }
63
+ return {
64
+ ...state,
65
+ [key]: atomToken,
66
+ }
67
+ })
68
+ })
69
+ },
70
+ ],
71
+ })
72
+ return selector({
73
+ key: `👁‍🗨_atom_token_index`,
74
+ get: ({ get }) => get(atomTokenIndexState__INTERNAL),
75
+ })
76
+ }
77
+
78
+ export const attachMetaSelectors = (
79
+ store: Store = IMPLICIT.STORE
80
+ ): ReadonlySelectorToken<SelectorTokenIndex> => {
81
+ const readonlySelectorTokenIndexState__INTERNAL = atom<SelectorTokenIndex>({
82
+ key: `👁‍🗨_selector_token_index__INTERNAL`,
83
+ default: () =>
84
+ Object.assign(
85
+ [...store.readonlySelectors].reduce<SelectorTokenIndex>((acc, [key]) => {
86
+ acc[key] = { key, type: `readonly_selector` }
87
+ return acc
88
+ }, {}),
89
+ [...store.selectors].reduce<SelectorTokenIndex>((acc, [key]) => {
90
+ acc[key] = { key, type: `selector` }
91
+ return acc
92
+ }, {})
93
+ ),
94
+ effects: [
95
+ ({ setSelf }) => {
96
+ store.subject.selectorCreation.subscribe((selectorToken) => {
97
+ if (store.operation.open) {
98
+ return
99
+ }
100
+ setSelf((state) => {
101
+ const { key, family } = selectorToken
102
+ if (family) {
103
+ const { key: familyKey, subKey } = family
104
+ const current = state[familyKey]
105
+ if (current === undefined || `familyMembers` in current) {
106
+ const familyKeyState = current || {
107
+ key: familyKey,
108
+ familyMembers: {},
109
+ }
110
+ return {
111
+ ...state,
112
+ [familyKey]: {
113
+ ...familyKeyState,
114
+ familyMembers: {
115
+ ...familyKeyState.familyMembers,
116
+ [subKey]: selectorToken,
117
+ },
118
+ },
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ ...state,
124
+ [key]: selectorToken,
125
+ }
126
+ })
127
+ })
128
+ },
129
+ ],
130
+ })
131
+ return selector({
132
+ key: `👁‍🗨_selector_token_index`,
133
+ get: ({ get }) => get(readonlySelectorTokenIndexState__INTERNAL),
134
+ })
135
+ }
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1 @@
1
+ export {}
@@ -5,6 +5,7 @@ import type { Atom, ReadonlySelector, Selector } from "."
5
5
  import { target } from "."
6
6
  import type { Store } from "./store"
7
7
  import { IMPLICIT } from "./store"
8
+ import type { StateToken } from ".."
8
9
 
9
10
  export type OperationProgress =
10
11
  | {
@@ -14,21 +15,31 @@ export type OperationProgress =
14
15
  open: true
15
16
  done: Set<string>
16
17
  prev: Hamt<any, string>
18
+ time: number
19
+ token: StateToken<any>
17
20
  }
18
21
 
19
- export const openOperation = (store: Store): void => {
22
+ export const openOperation = (token: StateToken<any>, store: Store): void => {
20
23
  const core = target(store)
24
+ if (core.operation.open) {
25
+ store.config.logger?.error(
26
+ `❌ failed to setState to "${token.key}" during a setState for "${core.operation.token.key}"`
27
+ )
28
+ throw Symbol(`violation`)
29
+ }
21
30
  core.operation = {
22
31
  open: true,
23
32
  done: new Set(),
24
33
  prev: store.valueMap,
34
+ time: Date.now(),
35
+ token,
25
36
  }
26
- store.config.logger?.info(`⭕`, `operation start`)
37
+ store.config.logger?.info(`⭕ operation start from "${token.key}"`)
27
38
  }
28
39
  export const closeOperation = (store: Store): void => {
29
40
  const core = target(store)
30
41
  core.operation = { open: false }
31
- store.config.logger?.info(`🔴`, `operation done`)
42
+ store.config.logger?.info(`🔴 operation done`)
32
43
  }
33
44
 
34
45
  export const isDone = (key: string, store: Store = IMPLICIT.STORE): boolean => {
@@ -18,7 +18,7 @@ import type {
18
18
  AtomToken,
19
19
  FamilyMetadata,
20
20
  ReadonlySelectorOptions,
21
- ReadonlyValueToken,
21
+ ReadonlySelectorToken,
22
22
  SelectorOptions,
23
23
  SelectorToken,
24
24
  StateToken,
@@ -46,7 +46,7 @@ export const lookupSelectorSources = (
46
46
  store: Store
47
47
  ): (
48
48
  | AtomToken<unknown>
49
- | ReadonlyValueToken<unknown>
49
+ | ReadonlySelectorToken<unknown>
50
50
  | SelectorToken<unknown>
51
51
  )[] =>
52
52
  target(store)
@@ -56,7 +56,7 @@ export const lookupSelectorSources = (
56
56
 
57
57
  export const traceSelectorAtoms = (
58
58
  selectorKey: string,
59
- dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
59
+ dependency: ReadonlySelectorToken<unknown> | StateToken<unknown>,
60
60
  store: Store
61
61
  ): AtomToken<unknown>[] => {
62
62
  const roots: AtomToken<unknown>[] = []
@@ -97,12 +97,15 @@ export const traceAllSelectorAtoms = (
97
97
 
98
98
  export const updateSelectorAtoms = (
99
99
  selectorKey: string,
100
- dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
100
+ dependency: ReadonlySelectorToken<unknown> | StateToken<unknown>,
101
101
  store: Store
102
102
  ): void => {
103
103
  const core = target(store)
104
104
  if (dependency.type === `atom`) {
105
- core.selectorAtoms = core.selectorAtoms.set(selectorKey, dependency.key)
105
+ core.selectorAtoms = core.selectorAtoms.set({
106
+ selectorKey,
107
+ atomKey: dependency.key,
108
+ })
106
109
  store.config.logger?.info(
107
110
  ` || adding root for "${selectorKey}": ${dependency.key}`
108
111
  )
@@ -114,7 +117,10 @@ export const updateSelectorAtoms = (
114
117
  roots.map((r) => r.key)
115
118
  )
116
119
  for (const root of roots) {
117
- core.selectorAtoms = core.selectorAtoms.set(selectorKey, root.key)
120
+ core.selectorAtoms = core.selectorAtoms.set({
121
+ selectorKey,
122
+ atomKey: root.key,
123
+ })
118
124
  }
119
125
  }
120
126
 
@@ -138,12 +144,16 @@ export const registerSelector = (
138
144
  )
139
145
  } else {
140
146
  store.config.logger?.info(
141
- `🔌 registerSelector "${selectorKey}" <- "${dependency.key}" =`,
142
- dependencyValue
147
+ `🔌 registerSelector "${selectorKey}" <- ( "${dependency.key}" =`,
148
+ dependencyValue,
149
+ `)`
150
+ )
151
+ core.selectorGraph = core.selectorGraph.set(
152
+ { from: dependency.key, to: selectorKey },
153
+ {
154
+ source: dependency.key,
155
+ }
143
156
  )
144
- core.selectorGraph = core.selectorGraph.set(selectorKey, dependency.key, {
145
- source: dependency.key,
146
- })
147
157
  }
148
158
  updateSelectorAtoms(selectorKey, dependency, store)
149
159
  return dependencyValue
@@ -163,12 +173,12 @@ export function selector__INTERNAL<T>(
163
173
  options: ReadonlySelectorOptions<T>,
164
174
  family?: FamilyMetadata,
165
175
  store?: Store
166
- ): ReadonlyValueToken<T>
176
+ ): ReadonlySelectorToken<T>
167
177
  export function selector__INTERNAL<T>(
168
178
  options: ReadonlySelectorOptions<T> | SelectorOptions<T>,
169
179
  family?: FamilyMetadata,
170
180
  store: Store = IMPLICIT.STORE
171
- ): ReadonlyValueToken<T> | SelectorToken<T> {
181
+ ): ReadonlySelectorToken<T> | SelectorToken<T> {
172
182
  const core = target(store)
173
183
  if (HAMT.has(options.key, core.selectors)) {
174
184
  store.config.logger?.error(
@@ -199,7 +209,13 @@ export function selector__INTERNAL<T>(
199
209
  )
200
210
  const initialValue = getSelf()
201
211
  store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
202
- return { ...readonlySelector, type: `readonly_selector` }
212
+ const token: ReadonlySelectorToken<T> = {
213
+ key: options.key,
214
+ type: `readonly_selector`,
215
+ family,
216
+ }
217
+ store.subject.selectorCreation.next(token)
218
+ return token
203
219
  }
204
220
  const setSelf = (next: T | ((oldValue: T) => T)): void => {
205
221
  store.config.logger?.info(` <- "${options.key}" became`, next)
@@ -223,5 +239,11 @@ export function selector__INTERNAL<T>(
223
239
  core.selectors = HAMT.set(options.key, mySelector, core.selectors)
224
240
  const initialValue = getSelf()
225
241
  store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
226
- return { ...mySelector, type: `selector` }
242
+ const token: SelectorToken<T> = {
243
+ key: options.key,
244
+ type: `selector`,
245
+ family,
246
+ }
247
+ store.subject.selectorCreation.next(token)
248
+ return token
227
249
  }
@@ -1,5 +1,6 @@
1
1
  import type { Hamt } from "hamt_plus"
2
2
  import HAMT from "hamt_plus"
3
+ import * as Rx from "rxjs"
3
4
 
4
5
  import { doNothing } from "~/packages/anvl/src/function"
5
6
  import { Join } from "~/packages/anvl/src/join"
@@ -10,11 +11,19 @@ import type {
10
11
  ReadonlySelector,
11
12
  Selector,
12
13
  TransactionStatus,
13
- Logger,
14
14
  Timeline,
15
15
  TimelineData,
16
16
  } from "."
17
- import type { Transaction, ƒn } from ".."
17
+ import type {
18
+ AtomToken,
19
+ Logger,
20
+ ReadonlySelectorToken,
21
+ SelectorToken,
22
+ TimelineToken,
23
+ Transaction,
24
+ TransactionToken,
25
+ ƒn,
26
+ } from ".."
18
27
 
19
28
  export type StoreCore = Pick<
20
29
  Store,
@@ -35,15 +44,24 @@ export interface Store {
35
44
  atoms: Hamt<Atom<any>, string>
36
45
  atomsThatAreDefault: Set<string>
37
46
  readonlySelectors: Hamt<ReadonlySelector<any>, string>
38
- selectorAtoms: Join
47
+ selectorAtoms: Join<null, `selectorKey`, `atomKey`>
39
48
  selectorGraph: Join<{ source: string }>
40
49
  selectors: Hamt<Selector<any>, string>
41
50
  timelines: Hamt<Timeline, string>
42
- timelineAtoms: Join
51
+ timelineAtoms: Join<null, `timelineKey`, `atomKey`>
43
52
  timelineStore: Hamt<TimelineData, string>
44
53
  transactions: Hamt<Transaction<any>, string>
45
54
  valueMap: Hamt<any, string>
46
55
 
56
+ subject: {
57
+ atomCreation: Rx.Subject<AtomToken<unknown>>
58
+ selectorCreation: Rx.Subject<
59
+ ReadonlySelectorToken<unknown> | SelectorToken<unknown>
60
+ >
61
+ transactionCreation: Rx.Subject<TransactionToken<unknown>>
62
+ timelineCreation: Rx.Subject<TimelineToken>
63
+ }
64
+
47
65
  operation: OperationProgress
48
66
  transactionStatus: TransactionStatus<ƒn>
49
67
  config: {
@@ -58,15 +76,26 @@ export const createStore = (name: string): Store =>
58
76
  atoms: HAMT.make<Atom<any>, string>(),
59
77
  atomsThatAreDefault: new Set(),
60
78
  readonlySelectors: HAMT.make<ReadonlySelector<any>, string>(),
61
- selectorAtoms: new Join({ relationType: `n:n` }),
79
+ selectorAtoms: new Join({ relationType: `n:n` })
80
+ .from(`selectorKey`)
81
+ .to(`atomKey`),
62
82
  selectorGraph: new Join({ relationType: `n:n` }),
63
83
  selectors: HAMT.make<Selector<any>, string>(),
64
84
  timelines: HAMT.make<Timeline, string>(),
65
- timelineAtoms: new Join({ relationType: `1:n` }),
85
+ timelineAtoms: new Join({ relationType: `1:n` })
86
+ .from(`timelineKey`)
87
+ .to(`atomKey`),
66
88
  timelineStore: HAMT.make<TimelineData, string>(),
67
89
  transactions: HAMT.make<Transaction<any>, string>(),
68
90
  valueMap: HAMT.make<any, string>(),
69
91
 
92
+ subject: {
93
+ atomCreation: new Rx.Subject(),
94
+ selectorCreation: new Rx.Subject(),
95
+ transactionCreation: new Rx.Subject(),
96
+ timelineCreation: new Rx.Subject(),
97
+ },
98
+
70
99
  operation: {
71
100
  open: false,
72
101
  },
@@ -0,0 +1,89 @@
1
+ import type { Store } from "."
2
+ import { IMPLICIT } from "."
3
+ import { setState } from ".."
4
+ import type { TimelineToken } from ".."
5
+
6
+ export const redo__INTERNAL = (
7
+ token: TimelineToken,
8
+ store: Store = IMPLICIT.STORE
9
+ ): void => {
10
+ store.config.logger?.info(`⏩ redo "${token.key}"`)
11
+ const timelineData = store.timelineStore.get(token.key)
12
+ if (!timelineData) {
13
+ store.config.logger?.error(
14
+ `Failed to redo on timeline "${token.key}". This timeline has not been initialized.`
15
+ )
16
+ return
17
+ }
18
+ if (timelineData.at === timelineData.history.length) {
19
+ store.config.logger?.warn(
20
+ `Failed to redo at the end of timeline "${token.key}". There is nothing to redo.`
21
+ )
22
+ return
23
+ }
24
+ timelineData.timeTraveling = true
25
+ const update = timelineData.history[timelineData.at]
26
+ switch (update.type) {
27
+ case `atom_update`: {
28
+ const { key, newValue } = update
29
+ setState({ key, type: `atom` }, newValue)
30
+ break
31
+ }
32
+ case `selector_update`:
33
+ case `transaction_update`: {
34
+ for (const atomUpdate of update.atomUpdates) {
35
+ const { key, newValue } = atomUpdate
36
+ setState({ key, type: `atom` }, newValue)
37
+ }
38
+ break
39
+ }
40
+ }
41
+ ++timelineData.at
42
+ timelineData.timeTraveling = false
43
+ store.config.logger?.info(
44
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
45
+ )
46
+ }
47
+
48
+ export const undo__INTERNAL = (
49
+ token: TimelineToken,
50
+ store: Store = IMPLICIT.STORE
51
+ ): void => {
52
+ store.config.logger?.info(`⏪ undo "${token.key}"`)
53
+ const timelineData = store.timelineStore.get(token.key)
54
+ if (!timelineData) {
55
+ store.config.logger?.error(
56
+ `Failed to undo on timeline "${token.key}". This timeline has not been initialized.`
57
+ )
58
+ return
59
+ }
60
+ if (timelineData.at === 0) {
61
+ store.config.logger?.warn(
62
+ `Failed to undo at the beginning of timeline "${token.key}". There is nothing to undo.`
63
+ )
64
+ return
65
+ }
66
+ timelineData.timeTraveling = true
67
+
68
+ --timelineData.at
69
+ const update = timelineData.history[timelineData.at]
70
+ switch (update.type) {
71
+ case `atom_update`: {
72
+ const { key, oldValue } = update
73
+ setState({ key, type: `atom` }, oldValue)
74
+ break
75
+ }
76
+ case `selector_update`:
77
+ case `transaction_update`: {
78
+ for (const atomUpdate of update.atomUpdates) {
79
+ const { key, oldValue } = atomUpdate
80
+ setState({ key, type: `atom` }, oldValue)
81
+ }
82
+ break
83
+ }
84
+ }
85
+ timelineData.timeTraveling = false
86
+ store.config.logger?.info(
87
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
88
+ )
89
+ }