@tamagui/use-store 1.15.40
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 +91 -0
- package/dist/cjs/Store.js +99 -0
- package/dist/cjs/Store.js.map +6 -0
- package/dist/cjs/comparators.js +45 -0
- package/dist/cjs/comparators.js.map +6 -0
- package/dist/cjs/configureUseStore.js +34 -0
- package/dist/cjs/configureUseStore.js.map +6 -0
- package/dist/cjs/constants.js +35 -0
- package/dist/cjs/constants.js.map +6 -0
- package/dist/cjs/decorators.js +34 -0
- package/dist/cjs/decorators.js.map +6 -0
- package/dist/cjs/fastCompare.js +168 -0
- package/dist/cjs/fastCompare.js.map +6 -0
- package/dist/cjs/helpers.js +101 -0
- package/dist/cjs/helpers.js.map +6 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/interfaces.js +17 -0
- package/dist/cjs/interfaces.js.map +6 -0
- package/dist/cjs/reaction.js +78 -0
- package/dist/cjs/reaction.js.map +6 -0
- package/dist/cjs/selector.js +139 -0
- package/dist/cjs/selector.js.map +6 -0
- package/dist/cjs/useStore.js +534 -0
- package/dist/cjs/useStore.js.map +6 -0
- package/dist/cjs/useStoreDebug.js +73 -0
- package/dist/cjs/useStoreDebug.js.map +6 -0
- package/dist/esm/Store.js +69 -0
- package/dist/esm/Store.js.map +6 -0
- package/dist/esm/Store.mjs +69 -0
- package/dist/esm/Store.mjs.map +6 -0
- package/dist/esm/comparators.js +21 -0
- package/dist/esm/comparators.js.map +6 -0
- package/dist/esm/comparators.mjs +21 -0
- package/dist/esm/comparators.mjs.map +6 -0
- package/dist/esm/configureUseStore.js +9 -0
- package/dist/esm/configureUseStore.js.map +6 -0
- package/dist/esm/configureUseStore.mjs +9 -0
- package/dist/esm/configureUseStore.mjs.map +6 -0
- package/dist/esm/constants.js +10 -0
- package/dist/esm/constants.js.map +6 -0
- package/dist/esm/constants.mjs +10 -0
- package/dist/esm/constants.mjs.map +6 -0
- package/dist/esm/decorators.js +10 -0
- package/dist/esm/decorators.js.map +6 -0
- package/dist/esm/decorators.mjs +10 -0
- package/dist/esm/decorators.mjs.map +6 -0
- package/dist/esm/fastCompare.js +143 -0
- package/dist/esm/fastCompare.js.map +6 -0
- package/dist/esm/fastCompare.mjs +143 -0
- package/dist/esm/fastCompare.mjs.map +6 -0
- package/dist/esm/helpers.js +70 -0
- package/dist/esm/helpers.js.map +6 -0
- package/dist/esm/helpers.mjs +70 -0
- package/dist/esm/helpers.mjs.map +6 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +14 -0
- package/dist/esm/index.mjs.map +6 -0
- package/dist/esm/interfaces.js +1 -0
- package/dist/esm/interfaces.js.map +6 -0
- package/dist/esm/interfaces.mjs +1 -0
- package/dist/esm/interfaces.mjs.map +6 -0
- package/dist/esm/reaction.js +53 -0
- package/dist/esm/reaction.js.map +6 -0
- package/dist/esm/reaction.mjs +53 -0
- package/dist/esm/reaction.mjs.map +6 -0
- package/dist/esm/selector.js +114 -0
- package/dist/esm/selector.js.map +6 -0
- package/dist/esm/selector.mjs +114 -0
- package/dist/esm/selector.mjs.map +6 -0
- package/dist/esm/useStore.js +516 -0
- package/dist/esm/useStore.js.map +6 -0
- package/dist/esm/useStore.mjs +516 -0
- package/dist/esm/useStore.mjs.map +6 -0
- package/dist/esm/useStoreDebug.js +35 -0
- package/dist/esm/useStoreDebug.js.map +6 -0
- package/dist/esm/useStoreDebug.mjs +35 -0
- package/dist/esm/useStoreDebug.mjs.map +6 -0
- package/package.json +44 -0
- package/src/Store.tsx +77 -0
- package/src/comparators.tsx +18 -0
- package/src/configureUseStore.tsx +7 -0
- package/src/constants.tsx +6 -0
- package/src/decorators.tsx +8 -0
- package/src/helpers.tsx +87 -0
- package/src/index.ts +9 -0
- package/src/interfaces.tsx +32 -0
- package/src/reaction.tsx +109 -0
- package/src/selector.tsx +129 -0
- package/src/useStore.tsx +705 -0
- package/src/useStoreDebug.tsx +40 -0
- package/types/Store.d.ts +28 -0
- package/types/Store.d.ts.map +1 -0
- package/types/comparators.d.ts +6 -0
- package/types/comparators.d.ts.map +1 -0
- package/types/configureUseStore.d.ts +4 -0
- package/types/configureUseStore.d.ts.map +1 -0
- package/types/constants.d.ts +6 -0
- package/types/constants.d.ts.map +1 -0
- package/types/decorators.d.ts +3 -0
- package/types/decorators.d.ts.map +1 -0
- package/types/fastCompare.d.ts +13 -0
- package/types/fastCompare.d.ts.map +1 -0
- package/types/helpers.d.ts +13 -0
- package/types/helpers.d.ts.map +1 -0
- package/types/index.d.ts +10 -0
- package/types/index.d.ts.map +1 -0
- package/types/interfaces.d.ts +30 -0
- package/types/interfaces.d.ts.map +1 -0
- package/types/reaction.d.ts +4 -0
- package/types/reaction.d.ts.map +1 -0
- package/types/selector.d.ts +3 -0
- package/types/selector.d.ts.map +1 -0
- package/types/useStore.d.ts +18 -0
- package/types/useStore.d.ts.map +1 -0
- package/types/useStoreDebug.d.ts +7 -0
- package/types/useStoreDebug.d.ts.map +1 -0
package/src/useStore.tsx
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'
|
|
2
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
3
|
+
|
|
4
|
+
import { isEqualSubsetShallow } from './comparators'
|
|
5
|
+
import { configureOpts } from './configureUseStore'
|
|
6
|
+
import { UNWRAP_PROXY, defaultOptions } from './constants'
|
|
7
|
+
import {
|
|
8
|
+
UNWRAP_STORE_INFO,
|
|
9
|
+
cache,
|
|
10
|
+
getStoreDescriptors,
|
|
11
|
+
getStoreUid,
|
|
12
|
+
simpleStr,
|
|
13
|
+
} from './helpers'
|
|
14
|
+
import { Selector, StoreInfo, UseStoreOptions } from './interfaces'
|
|
15
|
+
import {
|
|
16
|
+
ADD_TRACKER,
|
|
17
|
+
SHOULD_DEBUG,
|
|
18
|
+
Store,
|
|
19
|
+
StoreTracker,
|
|
20
|
+
TRACK,
|
|
21
|
+
TRIGGER_UPDATE,
|
|
22
|
+
disableTracking,
|
|
23
|
+
setDisableStoreTracking,
|
|
24
|
+
} from './Store'
|
|
25
|
+
import {
|
|
26
|
+
DebugStores,
|
|
27
|
+
shouldDebug,
|
|
28
|
+
useCurrentComponent,
|
|
29
|
+
useDebugStoreComponent,
|
|
30
|
+
} from './useStoreDebug'
|
|
31
|
+
|
|
32
|
+
// sanity check types here
|
|
33
|
+
// class StoreTest extends Store<{ id: number }> {}
|
|
34
|
+
// const storeTest = createStore(StoreTest, { id: 3 })
|
|
35
|
+
// const useStoreTest = createUseStore(StoreTest)
|
|
36
|
+
// const useStoreSelectorTest = createUseStoreSelector(StoreTest, (s) => s.props.id)
|
|
37
|
+
// const num = useStoreSelectorTest({ id: 0 })
|
|
38
|
+
// const ya = useStoreTest({ id: 1 })
|
|
39
|
+
// const yb = useStoreTest({ id: 1 }, (x) => x.props.id)
|
|
40
|
+
// const z = useStore(StoreTest)
|
|
41
|
+
// const abc = useGlobalStore(storeTest)
|
|
42
|
+
// const abc2 = useGlobalStore(storeTest)
|
|
43
|
+
// const abcSel = useGlobalStoreSelector(storeTest, (x) => x.props.id)
|
|
44
|
+
|
|
45
|
+
const idFn = (_) => _
|
|
46
|
+
|
|
47
|
+
// no singleton, just react
|
|
48
|
+
export function useStore<A extends Store<B>, B extends Object>(
|
|
49
|
+
StoreKlass: (new (props: B) => A) | (new () => A),
|
|
50
|
+
props?: B,
|
|
51
|
+
options: UseStoreOptions<A, any> = defaultOptions
|
|
52
|
+
): A {
|
|
53
|
+
const selectorCb = useCallback(options.selector || idFn, [])
|
|
54
|
+
const selector = options.selector ? selectorCb : options.selector
|
|
55
|
+
|
|
56
|
+
if (options.debug) {
|
|
57
|
+
useDebugStoreComponent(StoreKlass)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// if (options.once) {
|
|
61
|
+
// const key = props ? getKey(props) : ''
|
|
62
|
+
// const info = useMemo(() => {
|
|
63
|
+
// return getOrCreateStoreInfo(StoreKlass, props, { avoidCache: true }, key)
|
|
64
|
+
// }, [key])
|
|
65
|
+
// return useStoreFromInfo(info, selector)
|
|
66
|
+
// }
|
|
67
|
+
|
|
68
|
+
const info = getOrCreateStoreInfo(StoreKlass, props)
|
|
69
|
+
return useStoreFromInfo(info, selector)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function useStoreDebug<A extends Store<B>, B extends Object>(
|
|
73
|
+
StoreKlass: (new (props: B) => A) | (new () => A),
|
|
74
|
+
props?: B,
|
|
75
|
+
selector?: any
|
|
76
|
+
): A {
|
|
77
|
+
useDebugStoreComponent(StoreKlass)
|
|
78
|
+
return useStore(StoreKlass, props, selector)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// singleton
|
|
82
|
+
export function createStore<A extends Store<B>, B extends Object>(
|
|
83
|
+
StoreKlass: new (props: B) => A | (new () => A),
|
|
84
|
+
props?: B
|
|
85
|
+
): A {
|
|
86
|
+
return getOrCreateStoreInfo(StoreKlass, props).store as any
|
|
87
|
+
}
|
|
88
|
+
// use singleton with react
|
|
89
|
+
// TODO selector support with types...
|
|
90
|
+
|
|
91
|
+
export function useGlobalStore<A extends Store<B>, B extends Object>(
|
|
92
|
+
instance: A,
|
|
93
|
+
debug?: boolean
|
|
94
|
+
): A {
|
|
95
|
+
const store = instance[UNWRAP_PROXY]
|
|
96
|
+
const uid = getStoreUid(store.constructor, store.props)
|
|
97
|
+
const info = cache.get(uid)
|
|
98
|
+
if (!info) {
|
|
99
|
+
throw new Error(`This store not created using createStore()`)
|
|
100
|
+
}
|
|
101
|
+
if (debug) {
|
|
102
|
+
useDebugStoreComponent(store.constructor)
|
|
103
|
+
}
|
|
104
|
+
return useStoreFromInfo(info)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useGlobalStoreSelector<
|
|
108
|
+
A extends Store<B>,
|
|
109
|
+
B extends Object,
|
|
110
|
+
Selector extends (store: A) => any
|
|
111
|
+
>(
|
|
112
|
+
instance: A,
|
|
113
|
+
selector: Selector,
|
|
114
|
+
debug?: boolean
|
|
115
|
+
): Selector extends (a: A) => infer C ? C : unknown {
|
|
116
|
+
const store = instance[UNWRAP_PROXY]
|
|
117
|
+
const uid = getStoreUid(store.constructor, store.props)
|
|
118
|
+
const info = cache.get(uid)
|
|
119
|
+
if (!info) {
|
|
120
|
+
throw new Error(`This store not created using createStore()`)
|
|
121
|
+
}
|
|
122
|
+
if (debug) {
|
|
123
|
+
useDebugStoreComponent(store.constructor)
|
|
124
|
+
}
|
|
125
|
+
return useStoreFromInfo(info, selector)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// for creating a usable store hook
|
|
129
|
+
export function createUseStore<Props, Store>(
|
|
130
|
+
StoreKlass: (new (props: Props) => Store) | (new () => Store)
|
|
131
|
+
) {
|
|
132
|
+
return function <Res, C extends Selector<Store, Res>, Props extends Object>(
|
|
133
|
+
props?: Props,
|
|
134
|
+
options?: UseStoreOptions
|
|
135
|
+
// super hacky workaround for now, ts is unknown to me tbh
|
|
136
|
+
): C extends Selector<any, infer B> ? (B extends Object ? B : Store) : Store {
|
|
137
|
+
return useStore(StoreKlass as any, props, options)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// for creating a usable selector hook
|
|
142
|
+
export function createUseStoreSelector<
|
|
143
|
+
A extends Store<Props>,
|
|
144
|
+
Props extends Object,
|
|
145
|
+
Selected
|
|
146
|
+
>(
|
|
147
|
+
StoreKlass: (new (props: Props) => A) | (new () => A),
|
|
148
|
+
selector: Selector<A, Selected>
|
|
149
|
+
): (props?: Props) => Selected {
|
|
150
|
+
return (props?: Props) => {
|
|
151
|
+
return useStore(StoreKlass, props, { selector }) as any
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// selector hook
|
|
156
|
+
export function useStoreSelector<
|
|
157
|
+
A extends Store<B>,
|
|
158
|
+
B extends Object,
|
|
159
|
+
S extends Selector<any, Selected>,
|
|
160
|
+
Selected
|
|
161
|
+
>(StoreKlass: (new (props: B) => A) | (new () => A), selector: S, props?: B): Selected {
|
|
162
|
+
return useStore(StoreKlass, props, { selector }) as any
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type StoreAccessTracker = (store: any) => void
|
|
166
|
+
const storeAccessTrackers = new Set<StoreAccessTracker>()
|
|
167
|
+
export function trackStoresAccess(cb: StoreAccessTracker) {
|
|
168
|
+
storeAccessTrackers.add(cb)
|
|
169
|
+
return () => {
|
|
170
|
+
storeAccessTrackers.delete(cb)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// TODO deprecate and replace with usePortal
|
|
175
|
+
// for ephemeral stores (alpha, not working correctly yet)
|
|
176
|
+
export function useStoreOnce<A extends Store<B>, B extends Object>(
|
|
177
|
+
StoreKlass: (new (props: B) => A) | (new () => A),
|
|
178
|
+
props?: B,
|
|
179
|
+
selector?: any
|
|
180
|
+
): A {
|
|
181
|
+
return useStore(StoreKlass, props, { selector, once: true })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// get non-singleton outside react (weird)
|
|
185
|
+
export function getStore<A extends Store<B>, B extends Object>(
|
|
186
|
+
StoreKlass: (new (props: B) => A) | (new () => A),
|
|
187
|
+
props?: B
|
|
188
|
+
): A {
|
|
189
|
+
return getOrCreateStoreInfo(StoreKlass, props).store as any
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getOrCreateStoreInfo(
|
|
193
|
+
StoreKlass: any,
|
|
194
|
+
props: any,
|
|
195
|
+
opts?: { avoidCache: boolean },
|
|
196
|
+
propsKeyCalculated?: string
|
|
197
|
+
) {
|
|
198
|
+
const uid = getStoreUid(StoreKlass, propsKeyCalculated ?? props)
|
|
199
|
+
|
|
200
|
+
if (!opts?.avoidCache) {
|
|
201
|
+
const cached = cache.get(uid)
|
|
202
|
+
if (cached) {
|
|
203
|
+
// warn if creating an already existing store!
|
|
204
|
+
// need to detect HMR more cleanly if possible
|
|
205
|
+
if (cached.storeInstance.constructor.toString() !== StoreKlass.toString()) {
|
|
206
|
+
console.warn(
|
|
207
|
+
'Error: Stores must have a unique name (ignore if this is a hot reload)'
|
|
208
|
+
)
|
|
209
|
+
} else {
|
|
210
|
+
return cached
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// init
|
|
216
|
+
const storeInstance = new StoreKlass(props!)
|
|
217
|
+
|
|
218
|
+
const getters = {}
|
|
219
|
+
const actions = {}
|
|
220
|
+
const stateKeys: string[] = []
|
|
221
|
+
const descriptors = getStoreDescriptors(storeInstance)
|
|
222
|
+
for (const key in descriptors) {
|
|
223
|
+
const descriptor = descriptors[key]
|
|
224
|
+
if (typeof descriptor.value === 'function') {
|
|
225
|
+
// actions
|
|
226
|
+
actions[key] = descriptor.value
|
|
227
|
+
} else if (typeof descriptor.get === 'function') {
|
|
228
|
+
getters[key] = descriptor.get
|
|
229
|
+
} else {
|
|
230
|
+
if (key !== 'props' && key[0] !== '_') {
|
|
231
|
+
stateKeys.push(key)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const keyComparators = storeInstance['_comparators']
|
|
237
|
+
const storeInfo = {
|
|
238
|
+
keyComparators,
|
|
239
|
+
storeInstance,
|
|
240
|
+
getters,
|
|
241
|
+
stateKeys,
|
|
242
|
+
actions,
|
|
243
|
+
gettersState: {
|
|
244
|
+
getCache: new Map<string, any>(),
|
|
245
|
+
depsToGetter: new Map<string, Set<string>>(),
|
|
246
|
+
curGetKeys: new Set<string>(),
|
|
247
|
+
isGetting: false,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const store = createProxiedStore(storeInfo)
|
|
252
|
+
|
|
253
|
+
// uses more memory when on
|
|
254
|
+
if (process.env.NODE_ENV === 'development') {
|
|
255
|
+
allStores[uid] = store
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// if has a mount function call it
|
|
259
|
+
store.mount?.()
|
|
260
|
+
|
|
261
|
+
const value: StoreInfo = {
|
|
262
|
+
...storeInfo,
|
|
263
|
+
store,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!opts?.avoidCache) {
|
|
267
|
+
cache.set(uid, value)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return value
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const allStores = {}
|
|
274
|
+
|
|
275
|
+
const emptyObj = {}
|
|
276
|
+
const selectKeys = (obj: any, keys: string[]) => {
|
|
277
|
+
if (!keys.length) {
|
|
278
|
+
return emptyObj
|
|
279
|
+
}
|
|
280
|
+
const res = {}
|
|
281
|
+
for (const key of keys) {
|
|
282
|
+
res[key] = obj[key]
|
|
283
|
+
}
|
|
284
|
+
return res
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let isInReaction = false
|
|
288
|
+
export const setIsInReaction = (val: boolean) => {
|
|
289
|
+
isInReaction = val
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function useStoreFromInfo(
|
|
293
|
+
info: StoreInfo,
|
|
294
|
+
userSelector?: Selector<any> | undefined
|
|
295
|
+
): any {
|
|
296
|
+
const { store } = info
|
|
297
|
+
if (!store) {
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
const internal = useRef<StoreTracker>()
|
|
301
|
+
const component = useCurrentComponent()
|
|
302
|
+
if (!internal.current) {
|
|
303
|
+
internal.current = {
|
|
304
|
+
component,
|
|
305
|
+
isTracking: false,
|
|
306
|
+
firstRun: true,
|
|
307
|
+
tracked: new Set<string>(),
|
|
308
|
+
dispose: null as any,
|
|
309
|
+
last: null,
|
|
310
|
+
lastKeys: null,
|
|
311
|
+
}
|
|
312
|
+
const dispose = store[ADD_TRACKER](internal.current)
|
|
313
|
+
internal.current.dispose = dispose
|
|
314
|
+
}
|
|
315
|
+
const curInternal = internal.current!
|
|
316
|
+
|
|
317
|
+
const shouldPrintDebug =
|
|
318
|
+
!!process.env.LOG_LEVEL &&
|
|
319
|
+
(configureOpts.logLevel === 'debug' || shouldDebug(component, info))
|
|
320
|
+
|
|
321
|
+
const getSnapshot = useCallback(() => {
|
|
322
|
+
const curInternal = internal.current!
|
|
323
|
+
const keys = curInternal.firstRun ? info.stateKeys : [...curInternal.tracked]
|
|
324
|
+
|
|
325
|
+
const nextKeys = `${store._version}${keys.join('')}${userSelector?.toString() || ''}`
|
|
326
|
+
if (nextKeys === curInternal.lastKeys) {
|
|
327
|
+
if (shouldPrintDebug) {
|
|
328
|
+
console.log('avoid update', nextKeys, curInternal.lastKeys)
|
|
329
|
+
}
|
|
330
|
+
return curInternal.last
|
|
331
|
+
}
|
|
332
|
+
curInternal.lastKeys = nextKeys
|
|
333
|
+
|
|
334
|
+
let snap: any
|
|
335
|
+
// dont track during selector
|
|
336
|
+
setDisableStoreTracking(store, true)
|
|
337
|
+
const last = curInternal.last
|
|
338
|
+
if (userSelector) {
|
|
339
|
+
snap = userSelector(store)
|
|
340
|
+
} else {
|
|
341
|
+
snap = selectKeys(store, keys)
|
|
342
|
+
}
|
|
343
|
+
setDisableStoreTracking(store, false)
|
|
344
|
+
|
|
345
|
+
// const isUnchanged = false
|
|
346
|
+
|
|
347
|
+
// this wasn't updating in AnimationsStore
|
|
348
|
+
const isUnchanged =
|
|
349
|
+
typeof last !== 'undefined' &&
|
|
350
|
+
isEqualSubsetShallow(last, snap, {
|
|
351
|
+
keyComparators: info.keyComparators,
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
if (shouldPrintDebug) {
|
|
355
|
+
// prettier-ignore
|
|
356
|
+
console.log('🌑 getSnapshot', { userSelector, info, isUnchanged, component, keys, snap, curInternal })
|
|
357
|
+
}
|
|
358
|
+
if (isUnchanged) {
|
|
359
|
+
return last
|
|
360
|
+
}
|
|
361
|
+
curInternal.last = snap
|
|
362
|
+
return snap
|
|
363
|
+
}, [])
|
|
364
|
+
|
|
365
|
+
const state = useSyncExternalStore(store.subscribe, getSnapshot)
|
|
366
|
+
|
|
367
|
+
// dispose tracker on unmount
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
return curInternal.dispose
|
|
370
|
+
}, [])
|
|
371
|
+
|
|
372
|
+
// we never allow removing selector
|
|
373
|
+
if (!userSelector) {
|
|
374
|
+
// before each render
|
|
375
|
+
curInternal.isTracking = true
|
|
376
|
+
|
|
377
|
+
// track access, runs after each render
|
|
378
|
+
useLayoutEffect(() => {
|
|
379
|
+
curInternal.isTracking = false
|
|
380
|
+
curInternal.firstRun = false
|
|
381
|
+
if (shouldPrintDebug) {
|
|
382
|
+
console.log('🌑 finish render, tracking', [...curInternal.tracked])
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
} else {
|
|
386
|
+
return state
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return new Proxy(store, {
|
|
390
|
+
get(target, key) {
|
|
391
|
+
// be sure to touch the value for tracking purposes
|
|
392
|
+
const curVal = Reflect.get(target, key)
|
|
393
|
+
// while in reactions, don't proxy to old state as they aren't inside of react render
|
|
394
|
+
if (isInReaction) {
|
|
395
|
+
return curVal
|
|
396
|
+
}
|
|
397
|
+
if (Reflect.has(state, key)) {
|
|
398
|
+
return Reflect.get(state, key)
|
|
399
|
+
}
|
|
400
|
+
return curVal
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let setters = new Set<any>()
|
|
406
|
+
const logStack = new Set<Set<any[]> | 'end'>()
|
|
407
|
+
|
|
408
|
+
function createProxiedStore(storeInfo: Omit<StoreInfo, 'store' | 'source'>) {
|
|
409
|
+
const { actions, storeInstance, getters, gettersState } = storeInfo
|
|
410
|
+
const { getCache, curGetKeys, depsToGetter } = gettersState
|
|
411
|
+
const constr = storeInstance.constructor
|
|
412
|
+
|
|
413
|
+
let didSet = false
|
|
414
|
+
let isInAction = false
|
|
415
|
+
const wrappedActions = {}
|
|
416
|
+
|
|
417
|
+
// pre-setup actions
|
|
418
|
+
for (const key in actions) {
|
|
419
|
+
if (key === 'subscribe') {
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// wrap action and call didSet after
|
|
424
|
+
const actionFn = actions[key]
|
|
425
|
+
|
|
426
|
+
// yes its odd but bug in router needs to be figured out to fix
|
|
427
|
+
// and all it does is deopt slightly and silence get
|
|
428
|
+
const isGetFn = key.startsWith('get')
|
|
429
|
+
|
|
430
|
+
// wrap actions for tracking
|
|
431
|
+
wrappedActions[key] = function useStoreAction(...args: any[]) {
|
|
432
|
+
let res: any
|
|
433
|
+
try {
|
|
434
|
+
if (isGetFn || gettersState.isGetting) {
|
|
435
|
+
return Reflect.apply(actionFn, proxiedStore, args)
|
|
436
|
+
}
|
|
437
|
+
if (process.env.NODE_ENV === 'development' && DebugStores.has(constr)) {
|
|
438
|
+
console.log('(debug) startAction', key, { isInAction })
|
|
439
|
+
}
|
|
440
|
+
// dumb for now
|
|
441
|
+
isInAction = true
|
|
442
|
+
res = Reflect.apply(actionFn, proxiedStore, args)
|
|
443
|
+
if (res instanceof Promise) {
|
|
444
|
+
return res.then(finishAction)
|
|
445
|
+
}
|
|
446
|
+
finishAction()
|
|
447
|
+
return res
|
|
448
|
+
} catch (err: any) {
|
|
449
|
+
console.error(err.message, err.stack)
|
|
450
|
+
return res
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// dev mode do nice logging
|
|
455
|
+
if (process.env.NODE_ENV === 'development') {
|
|
456
|
+
if (!key.startsWith('get') && !key.startsWith('_') && key !== 'subscribe') {
|
|
457
|
+
const ogAction = wrappedActions[key]
|
|
458
|
+
wrappedActions[key] = new Proxy(ogAction, {
|
|
459
|
+
apply(target, thisArg, args) {
|
|
460
|
+
const isDebugging = DebugStores.has(constr)
|
|
461
|
+
const shouldLog =
|
|
462
|
+
process.env.LOG_LEVEL !== '0' &&
|
|
463
|
+
(isDebugging || configureOpts.logLevel !== 'error')
|
|
464
|
+
|
|
465
|
+
if (!shouldLog) {
|
|
466
|
+
return Reflect.apply(target, thisArg, args)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
setters = new Set()
|
|
470
|
+
const curSetters = setters
|
|
471
|
+
const isTopLevelLogger = logStack.size == 0
|
|
472
|
+
const logs = new Set<any[]>()
|
|
473
|
+
logStack.add(logs)
|
|
474
|
+
let res
|
|
475
|
+
const id = counter++
|
|
476
|
+
try {
|
|
477
|
+
// 🏃♀️ run action here now
|
|
478
|
+
res = Reflect.apply(target, thisArg, args)
|
|
479
|
+
} finally {
|
|
480
|
+
logStack.add('end')
|
|
481
|
+
|
|
482
|
+
const name = constr.name
|
|
483
|
+
const color = strColor(name)
|
|
484
|
+
const simpleArgs = args.map(simpleStr)
|
|
485
|
+
logs.add([
|
|
486
|
+
`%c 🌑 ${id} ${name.padStart(
|
|
487
|
+
isTopLevelLogger ? 8 : 4
|
|
488
|
+
)}%c.${key}(${simpleArgs.join(', ')})${
|
|
489
|
+
isTopLevelLogger && logStack.size > 1 ? ` (+${logStack.size - 1})` : ''
|
|
490
|
+
}`,
|
|
491
|
+
`color: ${color};`,
|
|
492
|
+
'color: black;',
|
|
493
|
+
])
|
|
494
|
+
if (curSetters.size) {
|
|
495
|
+
curSetters.forEach(({ key, value }) => {
|
|
496
|
+
if (
|
|
497
|
+
typeof value === 'string' ||
|
|
498
|
+
typeof value === 'number' ||
|
|
499
|
+
typeof value === 'boolean'
|
|
500
|
+
) {
|
|
501
|
+
logs.add([` SET ${key} ${value}`, value])
|
|
502
|
+
} else {
|
|
503
|
+
logs.add([` SET ${key}`, value])
|
|
504
|
+
}
|
|
505
|
+
})
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (isTopLevelLogger) {
|
|
509
|
+
let error = null
|
|
510
|
+
try {
|
|
511
|
+
for (const item of [...logStack]) {
|
|
512
|
+
if (item === 'end') {
|
|
513
|
+
console.groupEnd()
|
|
514
|
+
continue
|
|
515
|
+
}
|
|
516
|
+
const [head, ...rest] = item
|
|
517
|
+
if (head) {
|
|
518
|
+
console.groupCollapsed(...head)
|
|
519
|
+
console.groupCollapsed('...')
|
|
520
|
+
console.log('args', args)
|
|
521
|
+
console.log('response', res)
|
|
522
|
+
console.groupCollapsed('trace')
|
|
523
|
+
console.trace()
|
|
524
|
+
console.groupEnd()
|
|
525
|
+
console.groupEnd()
|
|
526
|
+
for (const [name, ...log] of rest) {
|
|
527
|
+
console.groupCollapsed(name)
|
|
528
|
+
console.log(...log)
|
|
529
|
+
console.groupEnd()
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
console.log('Weird log', head, ...rest)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch (err: any) {
|
|
536
|
+
error = err
|
|
537
|
+
}
|
|
538
|
+
for (const _ of [...logStack]) {
|
|
539
|
+
console.groupEnd()
|
|
540
|
+
}
|
|
541
|
+
if (error) {
|
|
542
|
+
console.error(`error loggin`, error)
|
|
543
|
+
}
|
|
544
|
+
logStack.clear()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// rome-ignore lint/correctness/noUnsafeFinally: ok
|
|
548
|
+
return res
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function hashCode(str: string) {
|
|
557
|
+
let hash = 0
|
|
558
|
+
for (let i = 0; i < str.length; i++) {
|
|
559
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash)
|
|
560
|
+
}
|
|
561
|
+
return hash
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function strColor(str: string) {
|
|
565
|
+
return `hsl(${hashCode(str) % 360}, 90%, 40%)`
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const finishAction = () => {
|
|
569
|
+
if (process.env.NODE_ENV === 'development' && DebugStores.has(constr)) {
|
|
570
|
+
console.log('(debug) finishAction', { didSet })
|
|
571
|
+
}
|
|
572
|
+
isInAction = false
|
|
573
|
+
if (didSet) {
|
|
574
|
+
storeInstance[TRIGGER_UPDATE]?.()
|
|
575
|
+
didSet = false
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const proxiedStore = new Proxy(storeInstance, {
|
|
580
|
+
// GET
|
|
581
|
+
get(_, key) {
|
|
582
|
+
// setup action one time
|
|
583
|
+
if (key in wrappedActions) {
|
|
584
|
+
return wrappedActions[key]
|
|
585
|
+
}
|
|
586
|
+
if (passThroughKeys[key]) {
|
|
587
|
+
return Reflect.get(storeInstance, key)
|
|
588
|
+
}
|
|
589
|
+
if (key === UNWRAP_PROXY) {
|
|
590
|
+
return storeInstance
|
|
591
|
+
}
|
|
592
|
+
if (key === UNWRAP_STORE_INFO) {
|
|
593
|
+
return storeInfo
|
|
594
|
+
}
|
|
595
|
+
if (disableTracking.get(storeInstance)) {
|
|
596
|
+
return Reflect.get(storeInstance, key)
|
|
597
|
+
}
|
|
598
|
+
if (typeof key !== 'string') {
|
|
599
|
+
return Reflect.get(storeInstance, key)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// non-actions...
|
|
603
|
+
|
|
604
|
+
if (storeAccessTrackers.size && !storeAccessTrackers.has(storeInstance)) {
|
|
605
|
+
for (const t of storeAccessTrackers) {
|
|
606
|
+
t(storeInstance)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const shouldPrintDebug =
|
|
611
|
+
process.env.NODE_ENV === 'development' && DebugStores.has(constr)
|
|
612
|
+
|
|
613
|
+
if (gettersState.isGetting) {
|
|
614
|
+
gettersState.curGetKeys.add(key)
|
|
615
|
+
} else {
|
|
616
|
+
storeInstance[TRACK](key, shouldPrintDebug)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (key in getters) {
|
|
620
|
+
if (getCache.has(key)) {
|
|
621
|
+
return getCache.get(key)
|
|
622
|
+
}
|
|
623
|
+
// track get deps
|
|
624
|
+
curGetKeys.clear()
|
|
625
|
+
const isSubGetter = gettersState.isGetting
|
|
626
|
+
gettersState.isGetting = true
|
|
627
|
+
const res = getters[key].call(proxiedStore)
|
|
628
|
+
if (!isSubGetter) {
|
|
629
|
+
gettersState.isGetting = false
|
|
630
|
+
}
|
|
631
|
+
// store inverse lookup
|
|
632
|
+
for (const gk of curGetKeys) {
|
|
633
|
+
if (!depsToGetter.has(gk)) {
|
|
634
|
+
depsToGetter.set(gk, new Set())
|
|
635
|
+
}
|
|
636
|
+
const cur = depsToGetter.get(gk)!
|
|
637
|
+
cur.add(key)
|
|
638
|
+
}
|
|
639
|
+
// TODO i added this !isSubGetter, seems logical but haven't validated
|
|
640
|
+
// has diff performance tradeoffs, not sure whats desirable
|
|
641
|
+
// if (!isSubGetter) {
|
|
642
|
+
getCache.set(key, res)
|
|
643
|
+
// }
|
|
644
|
+
return res
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return Reflect.get(storeInstance, key)
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
// SET
|
|
651
|
+
set(target, key, value, receiver) {
|
|
652
|
+
const cur = Reflect.get(target, key)
|
|
653
|
+
const res = Reflect.set(target, key, value, receiver)
|
|
654
|
+
// only update if changed, simple compare
|
|
655
|
+
if (res && cur !== value) {
|
|
656
|
+
// clear getters cache that rely on this
|
|
657
|
+
if (typeof key === 'string') {
|
|
658
|
+
clearGetterCache(key)
|
|
659
|
+
}
|
|
660
|
+
if (process.env.LOG_LEVEL && configureOpts.logLevel !== 'error') {
|
|
661
|
+
setters.add({ key, value })
|
|
662
|
+
if (storeInstance[SHOULD_DEBUG]()) {
|
|
663
|
+
console.log('(debug) SET', res, key, value)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (process.env.NODE_ENV === 'development' && DebugStores.has(constr)) {
|
|
667
|
+
console.log('SET...', { key, value, isInAction })
|
|
668
|
+
}
|
|
669
|
+
if (isInAction) {
|
|
670
|
+
didSet = true
|
|
671
|
+
} else {
|
|
672
|
+
storeInstance[TRIGGER_UPDATE]?.()
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return res
|
|
676
|
+
},
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
function clearGetterCache(setKey: string) {
|
|
680
|
+
const getters = depsToGetter.get(setKey)
|
|
681
|
+
getCache.delete(setKey)
|
|
682
|
+
if (!getters) {
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
for (const gk of getters) {
|
|
686
|
+
getCache.delete(gk)
|
|
687
|
+
if (depsToGetter.has(gk)) {
|
|
688
|
+
clearGetterCache(gk)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return proxiedStore
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
let counter = 0
|
|
697
|
+
|
|
698
|
+
const passThroughKeys = {
|
|
699
|
+
subscribe: true,
|
|
700
|
+
_version: true,
|
|
701
|
+
_trackers: true,
|
|
702
|
+
$$typeof: true,
|
|
703
|
+
_listeners: true,
|
|
704
|
+
_enableTracking: true,
|
|
705
|
+
}
|