@@ -29,6 +29,14 @@ const isSupportedObject = (x: unknown): x is object =>
29
29
type ProxyObject = object
30
30
const proxyCache = new WeakMap < object , ProxyObject > ( )
31
31
32
+ type Path = ( string | symbol ) [ ]
33
+ type Op =
34
+ | [ op : 'set' , path : Path , value : unknown , prevValue : unknown ]
35
+ | [ op : 'delete' , path : Path , prevValue : unknown ]
36
+ | [ op : 'resolve' , path : Path , value : unknown ]
37
+ | [ op : 'reject' , path : Path , error : unknown ]
38
+ type Listener = ( op : Op , nextVersion : number ) => void
39
+
32
40
let globalVersion = 1
33
41
const snapshotCache = new WeakMap <
34
42
object ,
@@ -44,16 +52,34 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
44
52
return found
45
53
}
46
54
let version = globalVersion
47
- const listeners = new Set < ( nextVersion : number ) => void > ( )
48
- const notifyUpdate = ( nextVersion ?: number ) => {
55
+ const listeners = new Set < Listener > ( )
56
+ const notifyUpdate = ( op : Op , nextVersion ?: number ) => {
49
57
if ( ! nextVersion ) {
50
58
nextVersion = ++ globalVersion
51
59
}
52
60
if ( version !== nextVersion ) {
53
61
version = nextVersion
54
- listeners . forEach ( ( listener ) => listener ( nextVersion as number ) )
62
+ listeners . forEach ( ( listener ) => listener ( op , nextVersion as number ) )
55
63
}
56
64
}
65
+ const propListeners = new Map < string | symbol , Listener > ( )
66
+ const getPropListener = ( prop : string | symbol ) => {
67
+ let propListener = propListeners . get ( prop )
68
+ if ( ! propListener ) {
69
+ propListener = ( op , nextVersion ) => {
70
+ const newOp : Op = [ ...op ]
71
+ newOp [ 1 ] = [ prop , ...( newOp [ 1 ] as Path ) ]
72
+ notifyUpdate ( newOp , nextVersion )
73
+ }
74
+ propListeners . set ( prop , propListener )
75
+ }
76
+ return propListener
77
+ }
78
+ const popPropListener = ( prop : string | symbol ) => {
79
+ const propListener = propListeners . get ( prop )
80
+ propListeners . delete ( prop )
81
+ return propListener
82
+ }
57
83
const createSnapshot = ( target : any , receiver : any ) => {
58
84
const cache = snapshotCache . get ( receiver )
59
85
if ( cache ?. [ 0 ] === version ) {
@@ -111,11 +137,11 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
111
137
const prevValue = target [ prop ]
112
138
const childListeners = ( prevValue as any ) ?. [ LISTENERS ]
113
139
if ( childListeners ) {
114
- childListeners . delete ( notifyUpdate )
140
+ childListeners . delete ( popPropListener ( prop ) )
115
141
}
116
142
const deleted = Reflect . deleteProperty ( target , prop )
117
143
if ( deleted ) {
118
- notifyUpdate ( )
144
+ notifyUpdate ( [ 'delete' , [ prop ] , prevValue ] )
119
145
}
120
146
return deleted
121
147
} ,
@@ -126,7 +152,7 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
126
152
}
127
153
const childListeners = ( prevValue as any ) ?. [ LISTENERS ]
128
154
if ( childListeners ) {
129
- childListeners . delete ( notifyUpdate )
155
+ childListeners . delete ( popPropListener ( prop ) )
130
156
}
131
157
if (
132
158
refSet . has ( value ) ||
@@ -138,12 +164,12 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
138
164
target [ prop ] = value
139
165
. then ( ( v ) => {
140
166
target [ prop ] [ PROMISE_RESULT ] = v
141
- notifyUpdate ( )
167
+ notifyUpdate ( [ 'resolve' , [ prop ] , v ] )
142
168
return v
143
169
} )
144
170
. catch ( ( e ) => {
145
171
target [ prop ] [ PROMISE_ERROR ] = e
146
- notifyUpdate ( )
172
+ notifyUpdate ( [ 'reject' , [ prop ] , e ] )
147
173
} )
148
174
} else {
149
175
value = getUntracked ( value ) || value
@@ -152,9 +178,9 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
152
178
} else {
153
179
target [ prop ] = proxy ( value )
154
180
}
155
- target [ prop ] [ LISTENERS ] . add ( notifyUpdate )
181
+ target [ prop ] [ LISTENERS ] . add ( getPropListener ( prop ) )
156
182
}
157
- notifyUpdate ( )
183
+ notifyUpdate ( [ 'set' , [ prop ] , value , prevValue ] )
158
184
return true
159
185
} ,
160
186
} )
@@ -186,7 +212,7 @@ export const getVersion = (proxyObject: any): number => {
186
212
187
213
export const subscribe = (
188
214
proxyObject : any ,
189
- callback : ( ) => void ,
215
+ callback : ( ops : Op [ ] ) => void ,
190
216
notifyInSync ?: boolean
191
217
) => {
192
218
if (
@@ -197,15 +223,17 @@ export const subscribe = (
197
223
throw new Error ( 'Please use proxy object' )
198
224
}
199
225
let pendingVersion = 0
200
- const listener = ( nextVersion : number ) => {
226
+ const ops : Op [ ] = [ ]
227
+ const listener : Listener = ( op , nextVersion ) => {
228
+ ops . push ( op )
201
229
if ( notifyInSync ) {
202
- callback ( )
230
+ callback ( ops . splice ( 0 ) )
203
231
return
204
232
}
205
233
pendingVersion = nextVersion
206
234
Promise . resolve ( ) . then ( ( ) => {
207
235
if ( nextVersion === pendingVersion ) {
208
- callback ( )
236
+ callback ( ops . splice ( 0 ) )
209
237
}
210
238
} )
211
239
}
0 commit comments