5
5
import { _backburner } from '@ember/runloop' ;
6
6
7
7
import { LOG_METRIC_COUNTS , LOG_NOTIFICATIONS } from '@warp-drive/build-config/debugging' ;
8
- import { DEBUG } from '@warp-drive/build-config/env' ;
9
8
import { assert } from '@warp-drive/build-config/macros' ;
10
9
import type { StableDocumentIdentifier , StableRecordIdentifier } from '@warp-drive/core-types/identifier' ;
11
10
@@ -14,14 +13,14 @@ import { log } from '../debug/utils';
14
13
import type { Store } from '../store-service' ;
15
14
16
15
export type UnsubscribeToken = object ;
17
- let tokenId = 0 ;
18
16
19
- const CacheOperations = new Set ( [ 'added' , 'removed' , 'state' , 'updated' , 'invalidated' ] ) ;
20
17
export type CacheOperation = 'added' | 'removed' | 'updated' | 'state' ;
21
18
export type DocumentCacheOperation = 'invalidated' | 'added' | 'removed' | 'updated' | 'state' ;
22
19
23
20
function isCacheOperationValue ( value : NotificationType | DocumentCacheOperation ) : value is DocumentCacheOperation {
24
- return CacheOperations . has ( value ) ;
21
+ return (
22
+ value === 'added' || value === 'state' || value === 'updated' || value === 'removed' || value === 'invalidated'
23
+ ) ;
25
24
}
26
25
27
26
function runLoopIsFlushing ( ) : boolean {
@@ -54,25 +53,40 @@ function count(label: string) {
54
53
globalThis . __WarpDriveMetricCountData [ label ] = ( globalThis . __WarpDriveMetricCountData [ label ] || 0 ) + 1 ;
55
54
}
56
55
56
+ function asInternalToken ( token : unknown ) : asserts token is {
57
+ for : StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document' ;
58
+ } & ( NotificationCallback | ResourceOperationCallback | DocumentOperationCallback ) {
59
+ assert ( `Expected a token with a 'for' property` , token && typeof token === 'function' && 'for' in token ) ;
60
+ }
61
+
57
62
function _unsubscribe (
58
- tokens : Map < UnsubscribeToken , StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document' > ,
59
63
token : UnsubscribeToken ,
60
64
cache : Map <
61
65
'resource' | 'document' | StableDocumentIdentifier | StableRecordIdentifier ,
62
- Map < UnsubscribeToken , NotificationCallback | ResourceOperationCallback | DocumentOperationCallback >
66
+ Array < NotificationCallback | ResourceOperationCallback | DocumentOperationCallback >
63
67
>
64
68
) {
65
- const identifier = tokens . get ( token ) ;
69
+ asInternalToken ( token ) ;
70
+ const identifier = token . for ;
66
71
if ( LOG_NOTIFICATIONS ) {
67
72
if ( ! identifier ) {
68
73
// eslint-disable-next-line no-console
69
74
console . log ( 'Passed unknown unsubscribe token to unsubscribe' , identifier ) ;
70
75
}
71
76
}
72
77
if ( identifier ) {
73
- tokens . delete ( token ) ;
74
- const map = cache . get ( identifier ) ;
75
- map ?. delete ( token ) ;
78
+ const callbacks = cache . get ( identifier ) ;
79
+ if ( ! callbacks ) {
80
+ return ;
81
+ }
82
+
83
+ const index = callbacks . indexOf ( token ) ;
84
+ if ( index === - 1 ) {
85
+ assert ( `Cannot unsubscribe a token that is not subscribed` , index !== - 1 ) ;
86
+ return ;
87
+ }
88
+
89
+ callbacks . splice ( index , 1 ) ;
76
90
}
77
91
}
78
92
@@ -92,9 +106,8 @@ export default class NotificationManager {
92
106
declare _buffered : Map < StableDocumentIdentifier | StableRecordIdentifier , [ string , string | undefined ] [ ] > ;
93
107
declare _cache : Map <
94
108
StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document' ,
95
- Map < UnsubscribeToken , NotificationCallback | ResourceOperationCallback | DocumentOperationCallback >
109
+ Array < NotificationCallback | ResourceOperationCallback | DocumentOperationCallback >
96
110
> ;
97
- declare _tokens : Map < UnsubscribeToken , StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document' > ;
98
111
declare _hasFlush : boolean ;
99
112
declare _onFlushCB ?: ( ) => void ;
100
113
@@ -104,7 +117,6 @@ export default class NotificationManager {
104
117
this . _buffered = new Map ( ) ;
105
118
this . _hasFlush = false ;
106
119
this . _cache = new Map ( ) ;
107
- this . _tokens = new Map ( ) ;
108
120
}
109
121
110
122
/**
@@ -148,17 +160,20 @@ export default class NotificationManager {
148
160
isStableIdentifier ( identifier ) ||
149
161
isDocumentIdentifier ( identifier )
150
162
) ;
151
- let map = this . _cache . get ( identifier ) ;
152
-
153
- if ( ! map ) {
154
- map = new Map ( ) ;
155
- this . _cache . set ( identifier , map ) ;
163
+ let callbacks = this . _cache . get ( identifier ) ;
164
+ assert ( `expected to receive a valid callback` , typeof callback === 'function' ) ;
165
+ assert ( `cannot subscribe with the same callback twice` , ! callbacks || ! callbacks . includes ( callback ) ) ;
166
+ // we use the callback as the cancellation token
167
+ //@ts -expect-error
168
+ callback . for = identifier ;
169
+
170
+ if ( ! callbacks ) {
171
+ callbacks = [ ] ;
172
+ this . _cache . set ( identifier , callbacks ) ;
156
173
}
157
174
158
- const unsubToken = DEBUG ? { _tokenRef : tokenId ++ } : { } ;
159
- map . set ( unsubToken , callback ) ;
160
- this . _tokens . set ( unsubToken , identifier ) ;
161
- return unsubToken ;
175
+ callbacks . push ( callback ) ;
176
+ return callback ;
162
177
}
163
178
164
179
/**
@@ -170,7 +185,7 @@ export default class NotificationManager {
170
185
*/
171
186
unsubscribe ( token : UnsubscribeToken ) {
172
187
if ( ! this . isDestroyed ) {
173
- _unsubscribe ( this . _tokens , token , this . _cache ) ;
188
+ _unsubscribe ( token , this . _cache ) ;
174
189
}
175
190
}
176
191
@@ -210,7 +225,7 @@ export default class NotificationManager {
210
225
return false ;
211
226
}
212
227
213
- const hasSubscribers = Boolean ( this . _cache . get ( identifier ) ?. size ) ;
228
+ const hasSubscribers = Boolean ( this . _cache . get ( identifier ) ?. length ) ;
214
229
215
230
if ( isCacheOperationValue ( value ) || hasSubscribers ) {
216
231
let buffer = this . _buffered . get ( identifier ) ;
@@ -302,8 +317,7 @@ export default class NotificationManager {
302
317
303
318
// TODO for documents this will need to switch based on Identifier kind
304
319
if ( isCacheOperationValue ( value ) ) {
305
- const callbackMap = this . _cache . get ( isDocumentIdentifier ( identifier ) ? 'document' : 'resource' ) as Map <
306
- UnsubscribeToken ,
320
+ const callbackMap = this . _cache . get ( isDocumentIdentifier ( identifier ) ? 'document' : 'resource' ) as Array <
307
321
ResourceOperationCallback | DocumentOperationCallback
308
322
> ;
309
323
@@ -314,11 +328,11 @@ export default class NotificationManager {
314
328
}
315
329
}
316
330
317
- const callbackMap = this . _cache . get ( identifier ) ;
318
- if ( ! callbackMap || ! callbackMap . size ) {
331
+ const callbacks = this . _cache . get ( identifier ) ;
332
+ if ( ! callbacks || ! callbacks . length ) {
319
333
return false ;
320
334
}
321
- callbackMap . forEach ( ( cb ) => {
335
+ callbacks . forEach ( ( cb ) => {
322
336
// @ts -expect-error overload doesn't narrow within body
323
337
cb ( identifier , value , key ) ;
324
338
} ) ;
@@ -327,7 +341,6 @@ export default class NotificationManager {
327
341
328
342
destroy ( ) {
329
343
this . isDestroyed = true ;
330
- this . _tokens . clear ( ) ;
331
344
this . _cache . clear ( ) ;
332
345
}
333
346
}
0 commit comments