@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.
Files changed (118) hide show
  1. package/README.md +91 -0
  2. package/dist/cjs/Store.js +99 -0
  3. package/dist/cjs/Store.js.map +6 -0
  4. package/dist/cjs/comparators.js +45 -0
  5. package/dist/cjs/comparators.js.map +6 -0
  6. package/dist/cjs/configureUseStore.js +34 -0
  7. package/dist/cjs/configureUseStore.js.map +6 -0
  8. package/dist/cjs/constants.js +35 -0
  9. package/dist/cjs/constants.js.map +6 -0
  10. package/dist/cjs/decorators.js +34 -0
  11. package/dist/cjs/decorators.js.map +6 -0
  12. package/dist/cjs/fastCompare.js +168 -0
  13. package/dist/cjs/fastCompare.js.map +6 -0
  14. package/dist/cjs/helpers.js +101 -0
  15. package/dist/cjs/helpers.js.map +6 -0
  16. package/dist/cjs/index.js +47 -0
  17. package/dist/cjs/index.js.map +6 -0
  18. package/dist/cjs/interfaces.js +17 -0
  19. package/dist/cjs/interfaces.js.map +6 -0
  20. package/dist/cjs/reaction.js +78 -0
  21. package/dist/cjs/reaction.js.map +6 -0
  22. package/dist/cjs/selector.js +139 -0
  23. package/dist/cjs/selector.js.map +6 -0
  24. package/dist/cjs/useStore.js +534 -0
  25. package/dist/cjs/useStore.js.map +6 -0
  26. package/dist/cjs/useStoreDebug.js +73 -0
  27. package/dist/cjs/useStoreDebug.js.map +6 -0
  28. package/dist/esm/Store.js +69 -0
  29. package/dist/esm/Store.js.map +6 -0
  30. package/dist/esm/Store.mjs +69 -0
  31. package/dist/esm/Store.mjs.map +6 -0
  32. package/dist/esm/comparators.js +21 -0
  33. package/dist/esm/comparators.js.map +6 -0
  34. package/dist/esm/comparators.mjs +21 -0
  35. package/dist/esm/comparators.mjs.map +6 -0
  36. package/dist/esm/configureUseStore.js +9 -0
  37. package/dist/esm/configureUseStore.js.map +6 -0
  38. package/dist/esm/configureUseStore.mjs +9 -0
  39. package/dist/esm/configureUseStore.mjs.map +6 -0
  40. package/dist/esm/constants.js +10 -0
  41. package/dist/esm/constants.js.map +6 -0
  42. package/dist/esm/constants.mjs +10 -0
  43. package/dist/esm/constants.mjs.map +6 -0
  44. package/dist/esm/decorators.js +10 -0
  45. package/dist/esm/decorators.js.map +6 -0
  46. package/dist/esm/decorators.mjs +10 -0
  47. package/dist/esm/decorators.mjs.map +6 -0
  48. package/dist/esm/fastCompare.js +143 -0
  49. package/dist/esm/fastCompare.js.map +6 -0
  50. package/dist/esm/fastCompare.mjs +143 -0
  51. package/dist/esm/fastCompare.mjs.map +6 -0
  52. package/dist/esm/helpers.js +70 -0
  53. package/dist/esm/helpers.js.map +6 -0
  54. package/dist/esm/helpers.mjs +70 -0
  55. package/dist/esm/helpers.mjs.map +6 -0
  56. package/dist/esm/index.js +14 -0
  57. package/dist/esm/index.js.map +6 -0
  58. package/dist/esm/index.mjs +14 -0
  59. package/dist/esm/index.mjs.map +6 -0
  60. package/dist/esm/interfaces.js +1 -0
  61. package/dist/esm/interfaces.js.map +6 -0
  62. package/dist/esm/interfaces.mjs +1 -0
  63. package/dist/esm/interfaces.mjs.map +6 -0
  64. package/dist/esm/reaction.js +53 -0
  65. package/dist/esm/reaction.js.map +6 -0
  66. package/dist/esm/reaction.mjs +53 -0
  67. package/dist/esm/reaction.mjs.map +6 -0
  68. package/dist/esm/selector.js +114 -0
  69. package/dist/esm/selector.js.map +6 -0
  70. package/dist/esm/selector.mjs +114 -0
  71. package/dist/esm/selector.mjs.map +6 -0
  72. package/dist/esm/useStore.js +516 -0
  73. package/dist/esm/useStore.js.map +6 -0
  74. package/dist/esm/useStore.mjs +516 -0
  75. package/dist/esm/useStore.mjs.map +6 -0
  76. package/dist/esm/useStoreDebug.js +35 -0
  77. package/dist/esm/useStoreDebug.js.map +6 -0
  78. package/dist/esm/useStoreDebug.mjs +35 -0
  79. package/dist/esm/useStoreDebug.mjs.map +6 -0
  80. package/package.json +44 -0
  81. package/src/Store.tsx +77 -0
  82. package/src/comparators.tsx +18 -0
  83. package/src/configureUseStore.tsx +7 -0
  84. package/src/constants.tsx +6 -0
  85. package/src/decorators.tsx +8 -0
  86. package/src/helpers.tsx +87 -0
  87. package/src/index.ts +9 -0
  88. package/src/interfaces.tsx +32 -0
  89. package/src/reaction.tsx +109 -0
  90. package/src/selector.tsx +129 -0
  91. package/src/useStore.tsx +705 -0
  92. package/src/useStoreDebug.tsx +40 -0
  93. package/types/Store.d.ts +28 -0
  94. package/types/Store.d.ts.map +1 -0
  95. package/types/comparators.d.ts +6 -0
  96. package/types/comparators.d.ts.map +1 -0
  97. package/types/configureUseStore.d.ts +4 -0
  98. package/types/configureUseStore.d.ts.map +1 -0
  99. package/types/constants.d.ts +6 -0
  100. package/types/constants.d.ts.map +1 -0
  101. package/types/decorators.d.ts +3 -0
  102. package/types/decorators.d.ts.map +1 -0
  103. package/types/fastCompare.d.ts +13 -0
  104. package/types/fastCompare.d.ts.map +1 -0
  105. package/types/helpers.d.ts +13 -0
  106. package/types/helpers.d.ts.map +1 -0
  107. package/types/index.d.ts +10 -0
  108. package/types/index.d.ts.map +1 -0
  109. package/types/interfaces.d.ts +30 -0
  110. package/types/interfaces.d.ts.map +1 -0
  111. package/types/reaction.d.ts +4 -0
  112. package/types/reaction.d.ts.map +1 -0
  113. package/types/selector.d.ts +3 -0
  114. package/types/selector.d.ts.map +1 -0
  115. package/types/useStore.d.ts +18 -0
  116. package/types/useStore.d.ts.map +1 -0
  117. package/types/useStoreDebug.d.ts +7 -0
  118. package/types/useStoreDebug.d.ts.map +1 -0
@@ -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
+ }