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