atom.io 0.1.0 → 0.3.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 +10 -15
- package/dist/index.d.ts +505 -4
- package/dist/index.js +890 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +879 -316
- package/dist/index.mjs.map +1 -1
- package/package.json +42 -10
- package/react/dist/index.d.ts +22 -0
- package/react/dist/index.js +68 -0
- package/react/dist/index.js.map +1 -0
- package/react/dist/index.mjs +44 -0
- package/react/dist/index.mjs.map +1 -0
- package/react/package.json +15 -0
- package/src/atom.ts +43 -0
- package/src/index.ts +77 -0
- package/src/internal/atom-internal.ts +50 -0
- package/src/internal/families-internal.ts +142 -0
- package/src/internal/get.ts +107 -0
- package/src/internal/index.ts +12 -0
- package/src/internal/is-default.ts +35 -0
- package/src/internal/logger.ts +46 -0
- package/src/internal/operation.ts +132 -0
- package/src/internal/selector-internal.ts +227 -0
- package/src/internal/set.ts +102 -0
- package/src/internal/store.ts +97 -0
- package/src/internal/subscribe-internal.ts +77 -0
- package/src/internal/timeline-internal.ts +196 -0
- package/src/internal/transaction-internal.ts +175 -0
- package/src/react/index.ts +64 -0
- package/src/selector.ts +62 -0
- package/src/subscribe.ts +55 -0
- package/src/timeline.ts +34 -0
- package/src/transaction.ts +41 -0
- package/dist/index-3b5d305c.d.ts +0 -257
- package/dist/react/index.d.ts +0 -21
- package/dist/react/index.js +0 -780
- package/dist/react/index.js.map +0 -1
- package/dist/react/index.mjs +0 -751
- package/dist/react/index.mjs.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
|
|
3
|
+
import type { KeyedStateUpdate, TransactionUpdate, Store } from "."
|
|
4
|
+
import { IMPLICIT, withdraw } from "."
|
|
5
|
+
import { setState } from ".."
|
|
6
|
+
import type { AtomToken, TimelineOptions, TimelineToken, ƒn } from ".."
|
|
7
|
+
|
|
8
|
+
export type Timeline = {
|
|
9
|
+
key: string
|
|
10
|
+
type: `timeline`
|
|
11
|
+
next: () => void
|
|
12
|
+
prev: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TimelineStateUpdate = KeyedStateUpdate<unknown> & {
|
|
16
|
+
type: `state_update`
|
|
17
|
+
}
|
|
18
|
+
export type TimelineTransactionUpdate = TransactionUpdate<ƒn> & {
|
|
19
|
+
type: `transaction_update`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TimelineData = {
|
|
23
|
+
at: number
|
|
24
|
+
timeTraveling: boolean
|
|
25
|
+
history: (TimelineStateUpdate | TimelineTransactionUpdate)[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function timeline__INTERNAL(
|
|
29
|
+
options: TimelineOptions,
|
|
30
|
+
store: Store = IMPLICIT.STORE
|
|
31
|
+
): TimelineToken {
|
|
32
|
+
let incompleteTransactionKey: string | null = null
|
|
33
|
+
const timelineData: TimelineData = {
|
|
34
|
+
at: 0,
|
|
35
|
+
timeTraveling: false,
|
|
36
|
+
history: [],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const subscribeToAtom = (token: AtomToken<any>) => {
|
|
40
|
+
const state = withdraw(token, store)
|
|
41
|
+
state.subject.subscribe((update) => {
|
|
42
|
+
const storeCurrentTransactionKey =
|
|
43
|
+
store.transactionStatus.phase === `applying`
|
|
44
|
+
? store.transactionStatus.key
|
|
45
|
+
: null
|
|
46
|
+
store.config.logger?.info(
|
|
47
|
+
`⏳ timeline "${options.key}" saw atom "${token.key}" go (`,
|
|
48
|
+
update.oldValue,
|
|
49
|
+
`->`,
|
|
50
|
+
update.newValue,
|
|
51
|
+
storeCurrentTransactionKey
|
|
52
|
+
? `) in "${storeCurrentTransactionKey}"`
|
|
53
|
+
: `) independently`
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
storeCurrentTransactionKey &&
|
|
58
|
+
store.transactionStatus.phase === `applying`
|
|
59
|
+
) {
|
|
60
|
+
const currentTransaction = withdraw(
|
|
61
|
+
{ key: storeCurrentTransactionKey, type: `transaction` },
|
|
62
|
+
store
|
|
63
|
+
)
|
|
64
|
+
if (incompleteTransactionKey !== storeCurrentTransactionKey) {
|
|
65
|
+
if (incompleteTransactionKey) {
|
|
66
|
+
store.config.logger?.error(
|
|
67
|
+
`Timeline "${options.key}" was unable to resolve transaction "${incompleteTransactionKey}. This is probably a bug.`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
incompleteTransactionKey = storeCurrentTransactionKey
|
|
71
|
+
const subscription = currentTransaction.subject.subscribe((update) => {
|
|
72
|
+
if (timelineData.timeTraveling === false) {
|
|
73
|
+
timelineData.history.push({
|
|
74
|
+
type: `transaction_update`,
|
|
75
|
+
...update,
|
|
76
|
+
atomUpdates: update.atomUpdates.filter((atomUpdate) =>
|
|
77
|
+
options.atoms.some((atom) => atom.key === atomUpdate.key)
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
timelineData.at = timelineData.history.length
|
|
82
|
+
subscription.unsubscribe()
|
|
83
|
+
incompleteTransactionKey = null
|
|
84
|
+
store.config.logger?.info(
|
|
85
|
+
`⌛ timeline "${options.key}" pushed a transaction_update from "${update.key}"`
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
if (timelineData.timeTraveling === false) {
|
|
91
|
+
timelineData.history.push({
|
|
92
|
+
type: `state_update`,
|
|
93
|
+
key: token.key,
|
|
94
|
+
oldValue: update.oldValue,
|
|
95
|
+
newValue: update.newValue,
|
|
96
|
+
})
|
|
97
|
+
store.config.logger?.info(
|
|
98
|
+
`⌛ timeline "${options.key}" pushed a state_update to "${token.key}"`
|
|
99
|
+
)
|
|
100
|
+
timelineData.at = timelineData.history.length
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const tokenOrFamily of options.atoms) {
|
|
107
|
+
if (tokenOrFamily.type === `atom_family`) {
|
|
108
|
+
const family = tokenOrFamily
|
|
109
|
+
family.subject.subscribe((token) => subscribeToAtom(token))
|
|
110
|
+
} else {
|
|
111
|
+
const token = tokenOrFamily
|
|
112
|
+
subscribeToAtom(token)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
store.timelineStore = HAMT.set(options.key, timelineData, store.timelineStore)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
key: options.key,
|
|
120
|
+
type: `timeline`,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const redo__INTERNAL = (
|
|
125
|
+
token: TimelineToken,
|
|
126
|
+
store: Store = IMPLICIT.STORE
|
|
127
|
+
): void => {
|
|
128
|
+
const timelineData = store.timelineStore.get(token.key)
|
|
129
|
+
if (!timelineData) {
|
|
130
|
+
store.config.logger?.error(
|
|
131
|
+
`Tried to redo on timeline "${token.key}" has not been initialized.`
|
|
132
|
+
)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if (timelineData.at === timelineData.history.length) {
|
|
136
|
+
store.config.logger?.warn(
|
|
137
|
+
`Tried to redo on timeline "${token.key}" but there is nothing to redo.`
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
timelineData.timeTraveling = true
|
|
142
|
+
const update = timelineData.history[timelineData.at]
|
|
143
|
+
switch (update.type) {
|
|
144
|
+
case `state_update`: {
|
|
145
|
+
const { key, newValue } = update
|
|
146
|
+
setState({ key, type: `atom` }, newValue)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
case `transaction_update`: {
|
|
150
|
+
for (const atomUpdate of update.atomUpdates) {
|
|
151
|
+
const { key, newValue } = atomUpdate
|
|
152
|
+
setState({ key, type: `atom` }, newValue)
|
|
153
|
+
}
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
++timelineData.at
|
|
158
|
+
timelineData.timeTraveling = false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const undo__INTERNAL = (
|
|
162
|
+
token: TimelineToken,
|
|
163
|
+
store: Store = IMPLICIT.STORE
|
|
164
|
+
): void => {
|
|
165
|
+
const timelineData = store.timelineStore.get(token.key)
|
|
166
|
+
if (!timelineData) {
|
|
167
|
+
store.config.logger?.error(
|
|
168
|
+
`Tried to undo on timeline "${token.key}" has not been initialized.`
|
|
169
|
+
)
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
if (timelineData.at === 0) {
|
|
173
|
+
store.config.logger?.warn(
|
|
174
|
+
`Tried to undo on timeline "${token.key}" but there is nothing to undo.`
|
|
175
|
+
)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
timelineData.timeTraveling = true
|
|
179
|
+
--timelineData.at
|
|
180
|
+
const update = timelineData.history[timelineData.at]
|
|
181
|
+
switch (update.type) {
|
|
182
|
+
case `state_update`: {
|
|
183
|
+
const { key, oldValue } = update
|
|
184
|
+
setState({ key, type: `atom` }, oldValue)
|
|
185
|
+
break
|
|
186
|
+
}
|
|
187
|
+
case `transaction_update`: {
|
|
188
|
+
for (const atomUpdate of update.atomUpdates) {
|
|
189
|
+
const { key, oldValue } = atomUpdate
|
|
190
|
+
setState({ key, type: `atom` }, oldValue)
|
|
191
|
+
}
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
timelineData.timeTraveling = false
|
|
196
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
import * as Rx from "rxjs"
|
|
3
|
+
|
|
4
|
+
import type { Store, StoreCore } from "."
|
|
5
|
+
import { deposit, withdraw, IMPLICIT } from "."
|
|
6
|
+
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
|
+
|
|
19
|
+
export type KeyedStateUpdate<T> = StateUpdate<T> & {
|
|
20
|
+
key: string
|
|
21
|
+
}
|
|
22
|
+
export type TransactionUpdate<ƒ extends ƒn> = {
|
|
23
|
+
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}" (init)`
|
|
79
|
+
)
|
|
80
|
+
store.transactionStatus.phase = `applying`
|
|
81
|
+
store.transactionStatus.output = output
|
|
82
|
+
const { atomUpdates } = store.transactionStatus
|
|
83
|
+
for (const { key, oldValue, newValue } of atomUpdates) {
|
|
84
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
85
|
+
const state = withdraw(token, store)
|
|
86
|
+
setState(state, newValue, store)
|
|
87
|
+
}
|
|
88
|
+
const myTransaction = withdraw<ƒ>(
|
|
89
|
+
{ key: store.transactionStatus.key, type: `transaction` },
|
|
90
|
+
store
|
|
91
|
+
)
|
|
92
|
+
myTransaction.subject.next({
|
|
93
|
+
key: store.transactionStatus.key,
|
|
94
|
+
atomUpdates,
|
|
95
|
+
output,
|
|
96
|
+
params: store.transactionStatus.params as Parameters<ƒ>,
|
|
97
|
+
})
|
|
98
|
+
store.transactionStatus = { phase: `idle` }
|
|
99
|
+
store.config.logger?.info(`🛬`, `transaction done`)
|
|
100
|
+
}
|
|
101
|
+
export const undoTransactionUpdate = <ƒ extends ƒn>(
|
|
102
|
+
update: TransactionUpdate<ƒ>,
|
|
103
|
+
store: Store
|
|
104
|
+
): void => {
|
|
105
|
+
store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
|
|
106
|
+
for (const { key, oldValue, newValue } of update.atomUpdates) {
|
|
107
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
108
|
+
const state = withdraw(token, store)
|
|
109
|
+
setState(state, oldValue, store)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const redoTransactionUpdate = <ƒ extends ƒn>(
|
|
113
|
+
update: TransactionUpdate<ƒ>,
|
|
114
|
+
store: Store
|
|
115
|
+
): void => {
|
|
116
|
+
store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
|
|
117
|
+
for (const { key, oldValue, newValue } of update.atomUpdates) {
|
|
118
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
119
|
+
const state = withdraw(token, store)
|
|
120
|
+
setState(state, newValue, store)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const abortTransaction = (store: Store): void => {
|
|
125
|
+
if (store.transactionStatus.phase === `idle`) {
|
|
126
|
+
store.config.logger?.warn(
|
|
127
|
+
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
store.transactionStatus = { phase: `idle` }
|
|
132
|
+
store.config.logger?.info(`🪂`, `transaction fail`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function transaction__INTERNAL<ƒ extends ƒn>(
|
|
136
|
+
options: TransactionOptions<ƒ>,
|
|
137
|
+
store: Store = IMPLICIT.STORE
|
|
138
|
+
): TransactionToken<ƒ> {
|
|
139
|
+
const newTransaction: Transaction<ƒ> = {
|
|
140
|
+
key: options.key,
|
|
141
|
+
type: `transaction`,
|
|
142
|
+
run: (...params: Parameters<ƒ>) => {
|
|
143
|
+
buildTransaction(options.key, params, store)
|
|
144
|
+
try {
|
|
145
|
+
const output = options.do(
|
|
146
|
+
{
|
|
147
|
+
get: (token) => getState(token, store),
|
|
148
|
+
set: (token, value) => setState(token, value, store),
|
|
149
|
+
},
|
|
150
|
+
...params
|
|
151
|
+
)
|
|
152
|
+
applyTransaction(output, store)
|
|
153
|
+
return output
|
|
154
|
+
} catch (thrown) {
|
|
155
|
+
abortTransaction(store)
|
|
156
|
+
store.config.logger?.error(`Transaction ${options.key} failed`, thrown)
|
|
157
|
+
throw thrown
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
subject: new Rx.Subject(),
|
|
161
|
+
}
|
|
162
|
+
const core = target(store)
|
|
163
|
+
core.transactions = HAMT.set(
|
|
164
|
+
newTransaction.key,
|
|
165
|
+
newTransaction,
|
|
166
|
+
core.transactions
|
|
167
|
+
)
|
|
168
|
+
const token = deposit(newTransaction)
|
|
169
|
+
return token
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const target = (store: Store = IMPLICIT.STORE): StoreCore =>
|
|
173
|
+
store.transactionStatus.phase === `building`
|
|
174
|
+
? store.transactionStatus.core
|
|
175
|
+
: store
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type Preact from "preact/hooks"
|
|
2
|
+
|
|
3
|
+
import type React from "react"
|
|
4
|
+
|
|
5
|
+
import { subscribe, setState, __INTERNAL__ } from "atom.io"
|
|
6
|
+
import type { ReadonlyValueToken, StateToken } from "atom.io"
|
|
7
|
+
|
|
8
|
+
import type { Modifier } from "~/packages/anvl/src/function"
|
|
9
|
+
|
|
10
|
+
export type AtomStoreReactConfig = {
|
|
11
|
+
useState: typeof Preact.useState | typeof React.useState
|
|
12
|
+
useEffect: typeof Preact.useEffect | typeof React.useEffect
|
|
13
|
+
store?: __INTERNAL__.Store
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
17
|
+
export const composeStoreHooks = ({
|
|
18
|
+
useState,
|
|
19
|
+
useEffect,
|
|
20
|
+
store = __INTERNAL__.IMPLICIT.STORE,
|
|
21
|
+
}: AtomStoreReactConfig) => {
|
|
22
|
+
function useI<T>(token: StateToken<T>): (next: Modifier<T> | T) => void {
|
|
23
|
+
const updateState = (next: Modifier<T> | T) => setState(token, next, store)
|
|
24
|
+
return updateState
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function useO<T>(token: ReadonlyValueToken<T> | StateToken<T>): T {
|
|
28
|
+
const state = __INTERNAL__.withdraw(token, store)
|
|
29
|
+
const initialValue = __INTERNAL__.getState__INTERNAL(state, store)
|
|
30
|
+
const [current, dispatch] = useState(initialValue)
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const unsubscribe = subscribe(
|
|
33
|
+
token,
|
|
34
|
+
({ newValue, oldValue }) => {
|
|
35
|
+
if (oldValue !== newValue) {
|
|
36
|
+
dispatch(newValue)
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
store
|
|
40
|
+
)
|
|
41
|
+
return unsubscribe
|
|
42
|
+
}, [])
|
|
43
|
+
|
|
44
|
+
return current
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function useIO<T>(token: StateToken<T>): [T, (next: Modifier<T> | T) => void] {
|
|
48
|
+
return [useO(token), useI(token)]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function useStore<T>(
|
|
52
|
+
token: StateToken<T>
|
|
53
|
+
): [T, (next: Modifier<T> | T) => void]
|
|
54
|
+
function useStore<T>(token: ReadonlyValueToken<T>): T
|
|
55
|
+
function useStore<T>(
|
|
56
|
+
token: ReadonlyValueToken<T> | StateToken<T>
|
|
57
|
+
): T | [T, (next: Modifier<T> | T) => void] {
|
|
58
|
+
if (token.type === `readonly_selector`) {
|
|
59
|
+
return useO(token)
|
|
60
|
+
}
|
|
61
|
+
return useIO(token)
|
|
62
|
+
}
|
|
63
|
+
return { useI, useO, useIO, useStore }
|
|
64
|
+
}
|
package/src/selector.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type * as Rx from "rxjs"
|
|
2
|
+
|
|
3
|
+
import type { Serializable } from "~/packages/anvl/src/json"
|
|
4
|
+
|
|
5
|
+
import type { ReadonlyValueToken, SelectorToken } from "."
|
|
6
|
+
import { selectorFamily__INTERNAL, selector__INTERNAL } from "./internal"
|
|
7
|
+
import type { ReadonlyTransactors, Transactors } from "./transaction"
|
|
8
|
+
|
|
9
|
+
export type SelectorOptions<T> = {
|
|
10
|
+
key: string
|
|
11
|
+
get: (readonlyTransactors: ReadonlyTransactors) => T
|
|
12
|
+
set: (transactors: Transactors, newValue: T) => void
|
|
13
|
+
}
|
|
14
|
+
export type ReadonlySelectorOptions<T> = Omit<SelectorOptions<T>, `set`>
|
|
15
|
+
|
|
16
|
+
export function selector<T>(options: SelectorOptions<T>): SelectorToken<T>
|
|
17
|
+
export function selector<T>(
|
|
18
|
+
options: ReadonlySelectorOptions<T>
|
|
19
|
+
): ReadonlyValueToken<T>
|
|
20
|
+
export function selector<T>(
|
|
21
|
+
options: ReadonlySelectorOptions<T> | SelectorOptions<T>
|
|
22
|
+
): ReadonlyValueToken<T> | SelectorToken<T> {
|
|
23
|
+
return selector__INTERNAL(options)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type SelectorFamilyOptions<T, K extends Serializable> = {
|
|
27
|
+
key: string
|
|
28
|
+
get: (key: K) => (readonlyTransactors: ReadonlyTransactors) => T
|
|
29
|
+
set: (key: K) => (transactors: Transactors, newValue: T) => void
|
|
30
|
+
}
|
|
31
|
+
export type ReadonlySelectorFamilyOptions<T, K extends Serializable> = Omit<
|
|
32
|
+
SelectorFamilyOptions<T, K>,
|
|
33
|
+
`set`
|
|
34
|
+
>
|
|
35
|
+
|
|
36
|
+
export type SelectorFamily<T, K extends Serializable = Serializable> = ((
|
|
37
|
+
key: K
|
|
38
|
+
) => SelectorToken<T>) & {
|
|
39
|
+
key: string
|
|
40
|
+
type: `selector_family`
|
|
41
|
+
subject: Rx.Subject<SelectorToken<T>>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ReadonlySelectorFamily<T, K extends Serializable = Serializable> = ((
|
|
45
|
+
key: K
|
|
46
|
+
) => ReadonlyValueToken<T>) & {
|
|
47
|
+
key: string
|
|
48
|
+
type: `readonly_selector_family`
|
|
49
|
+
subject: Rx.Subject<ReadonlyValueToken<T>>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function selectorFamily<T, K extends Serializable>(
|
|
53
|
+
options: SelectorFamilyOptions<T, K>
|
|
54
|
+
): SelectorFamily<T, K>
|
|
55
|
+
export function selectorFamily<T, K extends Serializable>(
|
|
56
|
+
options: ReadonlySelectorFamilyOptions<T, K>
|
|
57
|
+
): ReadonlySelectorFamily<T, K>
|
|
58
|
+
export function selectorFamily<T, K extends Serializable>(
|
|
59
|
+
options: ReadonlySelectorFamilyOptions<T, K> | SelectorFamilyOptions<T, K>
|
|
60
|
+
): ReadonlySelectorFamily<T, K> | SelectorFamily<T, K> {
|
|
61
|
+
return selectorFamily__INTERNAL(options)
|
|
62
|
+
}
|
package/src/subscribe.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReadonlyValueToken, StateToken, TransactionToken, ƒn } from "."
|
|
2
|
+
import type { Store, TransactionUpdate } from "./internal"
|
|
3
|
+
import { IMPLICIT, subscribeToRootAtoms, withdraw } from "./internal"
|
|
4
|
+
|
|
5
|
+
export type StateUpdate<T> = { newValue: T; oldValue: T }
|
|
6
|
+
export type UpdateHandler<T> = (update: StateUpdate<T>) => void
|
|
7
|
+
|
|
8
|
+
export const subscribe = <T>(
|
|
9
|
+
token: ReadonlyValueToken<T> | StateToken<T>,
|
|
10
|
+
handleUpdate: UpdateHandler<T>,
|
|
11
|
+
store: Store = IMPLICIT.STORE
|
|
12
|
+
): (() => void) => {
|
|
13
|
+
const state = withdraw<T>(token, store)
|
|
14
|
+
const subscription = state.subject.subscribe(handleUpdate)
|
|
15
|
+
store.config.logger?.info(`👀 subscribe to "${state.key}"`)
|
|
16
|
+
const dependencySubscriptions =
|
|
17
|
+
`get` in state ? subscribeToRootAtoms(state, store) : null
|
|
18
|
+
|
|
19
|
+
const unsubscribe =
|
|
20
|
+
dependencySubscriptions === null
|
|
21
|
+
? () => {
|
|
22
|
+
store.config.logger?.info(`🙈 unsubscribe from "${state.key}"`)
|
|
23
|
+
subscription.unsubscribe()
|
|
24
|
+
}
|
|
25
|
+
: () => {
|
|
26
|
+
store.config.logger?.info(
|
|
27
|
+
`🙈 unsubscribe from "${state.key}" and its dependencies`
|
|
28
|
+
)
|
|
29
|
+
subscription.unsubscribe()
|
|
30
|
+
for (const dependencySubscription of dependencySubscriptions) {
|
|
31
|
+
dependencySubscription.unsubscribe()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return unsubscribe
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type TransactionUpdateHandler<ƒ extends ƒn> = (
|
|
39
|
+
data: TransactionUpdate<ƒ>
|
|
40
|
+
) => void
|
|
41
|
+
|
|
42
|
+
export const subscribeToTransaction = <ƒ extends ƒn>(
|
|
43
|
+
token: TransactionToken<ƒ>,
|
|
44
|
+
handleUpdate: TransactionUpdateHandler<ƒ>,
|
|
45
|
+
store = IMPLICIT.STORE
|
|
46
|
+
): (() => void) => {
|
|
47
|
+
const tx = withdraw(token, store)
|
|
48
|
+
store.config.logger?.info(`👀 subscribe to transaction "${token.key}"`)
|
|
49
|
+
const subscription = tx.subject.subscribe(handleUpdate)
|
|
50
|
+
const unsubscribe = () => {
|
|
51
|
+
store.config.logger?.info(`🙈 unsubscribe from transaction "${token.key}"`)
|
|
52
|
+
subscription.unsubscribe()
|
|
53
|
+
}
|
|
54
|
+
return unsubscribe
|
|
55
|
+
}
|
package/src/timeline.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
import type * as Rx from "rxjs"
|
|
3
|
+
|
|
4
|
+
import type { AtomFamily, AtomToken, ƒn } from "."
|
|
5
|
+
import { setState } from "."
|
|
6
|
+
import type { Store, KeyedStateUpdate, TransactionUpdate } from "./internal"
|
|
7
|
+
import { target, IMPLICIT, withdraw } from "./internal"
|
|
8
|
+
import {
|
|
9
|
+
redo__INTERNAL,
|
|
10
|
+
timeline__INTERNAL,
|
|
11
|
+
undo__INTERNAL,
|
|
12
|
+
} from "./internal/timeline-internal"
|
|
13
|
+
|
|
14
|
+
export type TimelineToken = {
|
|
15
|
+
key: string
|
|
16
|
+
type: `timeline`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TimelineOptions = {
|
|
20
|
+
key: string
|
|
21
|
+
atoms: (AtomFamily<any> | AtomToken<any>)[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const timeline = (options: TimelineOptions): TimelineToken => {
|
|
25
|
+
return timeline__INTERNAL(options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const redo = (token: TimelineToken): void => {
|
|
29
|
+
return redo__INTERNAL(token, IMPLICIT.STORE)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const undo = (token: TimelineToken): void => {
|
|
33
|
+
return undo__INTERNAL(token, IMPLICIT.STORE)
|
|
34
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type * as Rx from "rxjs"
|
|
2
|
+
|
|
3
|
+
import type { ReadonlyValueToken, StateToken, TransactionToken } from "."
|
|
4
|
+
import type { Store, TransactionUpdate } from "./internal"
|
|
5
|
+
import { IMPLICIT, transaction__INTERNAL, withdraw } from "./internal"
|
|
6
|
+
|
|
7
|
+
export type ƒn = (...parameters: any[]) => any
|
|
8
|
+
|
|
9
|
+
export type Transactors = {
|
|
10
|
+
get: <S>(state: ReadonlyValueToken<S> | StateToken<S>) => S
|
|
11
|
+
set: <S>(state: StateToken<S>, newValue: S | ((oldValue: S) => S)) => void
|
|
12
|
+
}
|
|
13
|
+
export type ReadonlyTransactors = Pick<Transactors, `get`>
|
|
14
|
+
|
|
15
|
+
export type Action<ƒ extends ƒn> = (
|
|
16
|
+
transactors: Transactors,
|
|
17
|
+
...parameters: Parameters<ƒ>
|
|
18
|
+
) => ReturnType<ƒ>
|
|
19
|
+
|
|
20
|
+
export type TransactionOptions<ƒ extends ƒn> = {
|
|
21
|
+
key: string
|
|
22
|
+
do: Action<ƒ>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Transaction<ƒ extends ƒn> = {
|
|
26
|
+
key: string
|
|
27
|
+
type: `transaction`
|
|
28
|
+
run: (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
|
|
29
|
+
subject: Rx.Subject<TransactionUpdate<ƒ>>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function transaction<ƒ extends ƒn>(
|
|
33
|
+
options: TransactionOptions<ƒ>
|
|
34
|
+
): TransactionToken<ƒ> {
|
|
35
|
+
return transaction__INTERNAL(options)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const runTransaction =
|
|
39
|
+
<ƒ extends ƒn>(token: TransactionToken<ƒ>, store: Store = IMPLICIT.STORE) =>
|
|
40
|
+
(...parameters: Parameters<ƒ>): ReturnType<ƒ> =>
|
|
41
|
+
withdraw(token, store).run(...parameters)
|