@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,8 @@
1
+ export type ComparisonFn = (a: any, b: any) => boolean
2
+
3
+ export function compare(comparator: ComparisonFn) {
4
+ return (target: any, propertyKey: string): any => {
5
+ target['_comparators'] = target['_comparators'] || {}
6
+ target['_comparators'][propertyKey] = comparator
7
+ }
8
+ }
@@ -0,0 +1,56 @@
1
+ import { simpleHash } from '@hanzogui/simple-hash'
2
+
3
+ import type { StoreInfo } from './interfaces'
4
+
5
+ export function getStoreUid(Constructor: any, props: string | object | void) {
6
+ return simpleHash(
7
+ `${Constructor}${
8
+ !props ? '' : typeof props === 'string' ? props : JSON.stringify(props)
9
+ }`,
10
+ 'strict'
11
+ )
12
+ }
13
+
14
+ export const UNWRAP_STORE_INFO = Symbol('UNWRAP_STORE_INFO')
15
+ export const cache = new Map<string, StoreInfo>()
16
+
17
+ export function getStoreDescriptors(storeInstance: any) {
18
+ const proto = Object.getPrototypeOf(storeInstance)
19
+ const instanceDescriptors = Object.getOwnPropertyDescriptors(storeInstance)
20
+ const protoDescriptors = Object.getOwnPropertyDescriptors(proto)
21
+ const descriptors = {
22
+ ...protoDescriptors,
23
+ ...instanceDescriptors,
24
+ }
25
+ // @ts-ignore
26
+ delete descriptors.constructor
27
+ return descriptors
28
+ }
29
+
30
+ export function get<A>(_: A, b?: any): A extends new (props?: any) => infer B ? B : A {
31
+ return _ as any
32
+ }
33
+
34
+ export function simpleStr(arg: any) {
35
+ if (process.env.NODE_ENV === 'development') {
36
+ return typeof arg === 'function'
37
+ ? 'fn'
38
+ : typeof arg === 'string'
39
+ ? `"${arg}"`
40
+ : !arg
41
+ ? arg
42
+ : typeof arg !== 'object'
43
+ ? arg
44
+ : Array.isArray(arg)
45
+ ? '[...]'
46
+ : `{...}`
47
+ }
48
+ return arg
49
+ }
50
+ // helper for debugging
51
+
52
+ export function getStoreDebugInfo(store: any) {
53
+ return (
54
+ store[UNWRAP_STORE_INFO] ?? cache.get(getStoreUid(store.constructor, store.props))
55
+ )
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './useStore'
2
+ export { configureUseStore } from './configureUseStore'
3
+ export * from './interfaces'
4
+ export * from './observe'
5
+ export { UNWRAP_PROXY } from './constants'
6
+ export * from './comparators'
7
+ export * from './decorators'
8
+
9
+ // to extend for prop types
10
+ export class Store<Props extends Record<string, any>> {
11
+ constructor(public props: Props) {}
12
+ }
@@ -0,0 +1,46 @@
1
+ import type { StoreTracker } from './useStore'
2
+
3
+ export type Selector<A = unknown, B = unknown> = (x: A) => B
4
+
5
+ export type UseStoreSelector<Store, Res> = (store: Store) => Res
6
+ export type UseStoreOptions<Store = any, SelectorRes = any> = {
7
+ debug?: boolean
8
+ selector?: UseStoreSelector<Store, SelectorRes>
9
+ once?: boolean
10
+ }
11
+
12
+ export interface Store<Props = Record<string, any> | null | undefined> {
13
+ new (...args: any[]): any
14
+ props: Props
15
+ }
16
+
17
+ export type StoreInfo<A = Store> = {
18
+ uid: string
19
+ keyComparators?: {
20
+ [key: string]: (a: any, b: any) => boolean
21
+ }
22
+ // proxied store
23
+ store: A
24
+ props: Record<string, any> | null
25
+ storeInstance: any
26
+ getters: { [key: string]: any }
27
+ actions: any
28
+ stateKeys: Set<string>
29
+ debug?: boolean
30
+ gettersState: {
31
+ getCache: Map<string, any>
32
+ depsToGetter: Map<string, Set<string>>
33
+ curGetKeys: Set<string>
34
+ isGetting: boolean
35
+ }
36
+ listeners: Set<Function>
37
+ trackers: Set<StoreTracker>
38
+ version: number
39
+ subscribe: (onChanged: () => void) => () => void
40
+ triggerUpdate: Function
41
+ disableTracking: boolean
42
+ }
43
+
44
+ export type UseStoreConfig = {
45
+ logLevel?: 'debug' | 'info' | 'error'
46
+ }
@@ -0,0 +1,160 @@
1
+ import React from 'react'
2
+
3
+ import { isEqualSubsetShallow } from './comparators'
4
+ import { UNWRAP_PROXY } from './constants'
5
+ import type { StoreInfo } from './interfaces'
6
+ import { trackStoresAccess } from './useStore'
7
+
8
+ const logUpdate =
9
+ process.env.NODE_ENV === 'development'
10
+ ? (fn: any, stores: any[], last: any, next: any) => {
11
+ const getStoreLogName = (store: any) => {
12
+ const str = store[UNWRAP_PROXY] ?? store
13
+ return `${str.constructor.name}${store.props?.id ? `:${store.props.id}` : ''}`
14
+ }
15
+ const storeNames = stores.map(getStoreLogName).join(', ')
16
+ const name = `🌑 ▶️ %c${fn.name} ${storeNames} () ${last} => ${next}`
17
+ console.groupCollapsed(name, 'color: tomato;')
18
+ console.groupCollapsed('trace >')
19
+ console.trace()
20
+ console.groupEnd()
21
+ console.info(' next', next)
22
+ console.groupEnd()
23
+ }
24
+ : null
25
+
26
+ export function observe(fn: () => any) {
27
+ let prev = getObserverValueAndStoresAccessed(fn)
28
+ let disposeValue: Function | null = null
29
+
30
+ const subscribe = () => {
31
+ const stores = [...prev.storeInfos]
32
+ return subscribeToStores(stores, () => {
33
+ disposeValue?.()
34
+ const next = getObserverValueAndStoresAccessed(fn)
35
+
36
+ if (typeof next.value === 'function') {
37
+ disposeValue = next.value
38
+ if (process.env.NODE_ENV === 'development') {
39
+ logUpdate!(fn, [...next.storeInfos], '(fn)', '(fn)')
40
+ }
41
+ return
42
+ }
43
+ if (
44
+ isEqualSubsetShallow(prev.storeInfos, next.storeInfos) &&
45
+ isEqualSubsetShallow(prev.value, next.value)
46
+ ) {
47
+ return
48
+ }
49
+ if (process.env.NODE_ENV === 'development') {
50
+ logUpdate!(fn, [...next.storeInfos], prev.value, next.value)
51
+ }
52
+ prev = next
53
+ dispose()
54
+ dispose = subscribe()
55
+ })
56
+ }
57
+
58
+ let dispose = subscribe()
59
+
60
+ return {
61
+ dispose: () => {
62
+ dispose()
63
+ disposeValue?.()
64
+ },
65
+ getValue: () => prev.value,
66
+ }
67
+ }
68
+
69
+ export function useObserve<A>(fn: () => A): A {
70
+ const [state, setState] = React.useState(() => {
71
+ return getObserverValueAndStoresAccessed(fn)
72
+ })
73
+
74
+ React.useEffect(() => {
75
+ let dispose
76
+ const unsub = subscribeToStores([...state.storeInfos], () => {
77
+ dispose?.()
78
+ const next = getObserverValueAndStoresAccessed(fn)
79
+
80
+ const nextStoreInfos = [...next.storeInfos]
81
+ const prevStoreInfos = [...state.storeInfos]
82
+
83
+ // return function === return disposable
84
+ if (typeof next.value === 'function') {
85
+ if (process.env.NODE_ENV === 'development') {
86
+ logUpdate!(fn, nextStoreInfos, '(fn)', '(fn)')
87
+ }
88
+ dispose = next.value
89
+ return
90
+ }
91
+
92
+ setState((prev) => {
93
+ if (
94
+ isEqualSubsetShallow(prevStoreInfos, nextStoreInfos) &&
95
+ isEqualSubsetShallow(prev.value, next.value)
96
+ ) {
97
+ return prev
98
+ }
99
+ if (process.env.NODE_ENV === 'development') {
100
+ logUpdate!(fn, nextStoreInfos, prev.value, next.value)
101
+ }
102
+ return next
103
+ })
104
+ })
105
+
106
+ return () => {
107
+ unsub()
108
+ dispose?.()
109
+ }
110
+ }, [[...state.storeInfos].map((i) => i.uid).join(',')])
111
+
112
+ return state.value
113
+ }
114
+
115
+ function getObserverValueAndStoresAccessed<A>(selector: () => A): {
116
+ value: A
117
+ storeInfos: Set<StoreInfo>
118
+ } {
119
+ const storeInfos = new Set<StoreInfo>()
120
+ const dispose = trackStoresAccess((storeInfo) => {
121
+ storeInfos.add(storeInfo)
122
+ })
123
+ const value = selector()
124
+ dispose()
125
+ return {
126
+ value,
127
+ storeInfos,
128
+ }
129
+ }
130
+
131
+ function subscribeToStores(storeInfos: StoreInfo[], onUpdate: () => any) {
132
+ const disposes: Function[] = []
133
+
134
+ // wrap onUpdate to avoid waterfall calls + avoid tracking during onUpdate
135
+ let isUpdating = false
136
+ const onUpdateDebouncedWithoutTracking = () => {
137
+ if (isUpdating) return
138
+ isUpdating = true
139
+ queueMicrotask(() => {
140
+ try {
141
+ for (const storeInfo of storeInfos) {
142
+ storeInfo.disableTracking = true
143
+ }
144
+ onUpdate()
145
+ } finally {
146
+ isUpdating = false
147
+ for (const storeInfo of storeInfos) {
148
+ storeInfo.disableTracking = false
149
+ }
150
+ }
151
+ })
152
+ }
153
+
154
+ for (const storeInfo of storeInfos) {
155
+ disposes.push(storeInfo.subscribe(onUpdateDebouncedWithoutTracking))
156
+ }
157
+ return () => {
158
+ disposes.forEach((x) => x())
159
+ }
160
+ }