@ember-data/store 5.2.0-alpha.5 → 5.2.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.
package/addon/-private.js CHANGED
@@ -1062,205 +1062,11 @@ function detectMerge(typesCache, identifier, data, newId, lids) {
1062
1062
  }
1063
1063
  return false;
1064
1064
  }
1065
- let tokenId = 0;
1066
- const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
1067
- function isCacheOperationValue(value) {
1068
- return CacheOperations.has(value);
1069
- }
1070
- function runLoopIsFlushing() {
1071
- //@ts-expect-error
1072
- return !!_backburner.currentInstance && _backburner._autorun !== true;
1073
- }
1074
- const Cache = new Map();
1075
- const Tokens = new Map();
1076
- // TODO this isn't importable anyway, remove and use a map on the manager?
1077
- function unsubscribe(token) {
1078
- let identifier = Tokens.get(token);
1079
- if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
1080
- if (!identifier) {
1081
- // eslint-disable-next-line no-console
1082
- console.log('Passed unknown unsubscribe token to unsubscribe', identifier);
1083
- }
1084
- }
1085
- if (identifier) {
1086
- Tokens.delete(token);
1087
- const map = Cache.get(identifier);
1088
- map?.delete(token);
1089
- }
1090
- }
1065
+ var _class$1, _descriptor$1;
1091
1066
 
1092
1067
  /**
1093
- * The NotificationManager provides the ability to subscribe to
1094
- * changes to Cache state.
1095
- *
1096
- * This Feature is what allows EmberData to create subscriptions that
1097
- * work with any framework or change-notification system.
1098
- *
1099
- * @class NotificationManager
1100
- * @public
1101
- */
1102
- class NotificationManager {
1103
- constructor(store) {
1104
- this.store = store;
1105
- this.isDestroyed = false;
1106
- this._buffered = new Map();
1107
- this._hasFlush = false;
1108
- }
1109
-
1110
- /**
1111
- * Subscribe to changes for a given resource identifier, resource addition/removal, or document addition/removal.
1112
- *
1113
- * ```ts
1114
- * export type CacheOperation = 'added' | 'removed' | 'updated' | 'state';
1115
- *
1116
- * export interface NotificationCallback {
1117
- * (identifier: StableRecordIdentifier, notificationType: 'attributes' | 'relationships', key?: string): void;
1118
- * (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'state'): void;
1119
- * (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
1120
- * }
1121
- * export interface ResourceOperationCallback {
1122
- * // resource updates
1123
- * (identifier: StableRecordIdentifier, notificationType: CacheOperation): void;
1124
- * }
1125
- * export interface DocumentOperationCallback {
1126
- * // document updates
1127
- * (identifier: StableDocumentIdentifier, notificationType: CacheOperation): void;
1128
- * }
1129
- * ```
1130
- *
1131
- * @method subscribe
1132
- * @public
1133
- * @param {StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document'} identifier
1134
- * @param {NotificationCallback | ResourceOperationCallback | DocumentOperationCallback} callback
1135
- * @returns {UnsubscribeToken} an opaque token to be used with unsubscribe
1136
- */
1137
-
1138
- subscribe(identifier, callback) {
1139
- assert(`Expected to receive a stable Identifier to subscribe to`, identifier === 'resource' || identifier === 'document' || isStableIdentifier(identifier) || isDocumentIdentifier(identifier));
1140
- let map = Cache.get(identifier);
1141
- if (!map) {
1142
- map = new Map();
1143
- Cache.set(identifier, map);
1144
- }
1145
- let unsubToken = macroCondition(getOwnConfig().env.DEBUG) ? {
1146
- _tokenRef: tokenId++
1147
- } : {};
1148
- map.set(unsubToken, callback);
1149
- Tokens.set(unsubToken, identifier);
1150
- return unsubToken;
1151
- }
1152
-
1153
- /**
1154
- * remove a previous subscription
1155
- *
1156
- * @method unsubscribe
1157
- * @public
1158
- * @param {UnsubscribeToken} token
1159
- */
1160
- unsubscribe(token) {
1161
- if (!this.isDestroyed) {
1162
- unsubscribe(token);
1163
- }
1164
- }
1165
-
1166
- /**
1167
- * Custom Caches and Application Code should not call this method directly.
1168
- *
1169
- * @method notify
1170
- * @param identifier
1171
- * @param value
1172
- * @param key
1173
- * @return {Boolean} whether a notification was delivered to any subscribers
1174
- * @private
1175
- */
1176
-
1177
- notify(identifier, value, key) {
1178
- assert(`Notify does not accept a key argument for the namespace '${value}'. Received key '${key || ''}'.`, !key || value === 'attributes' || value === 'relationships');
1179
- if (!isStableIdentifier(identifier) && !isDocumentIdentifier(identifier)) {
1180
- if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
1181
- // eslint-disable-next-line no-console
1182
- console.log(`Notifying: Expected to receive a stable Identifier to notify '${value}' '${key || ''}' with, but ${String(identifier)} is not in the cache`, identifier);
1183
- }
1184
- return false;
1185
- }
1186
- if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
1187
- // eslint-disable-next-line no-console
1188
- console.log(`Buffering Notify: ${String(identifier.lid)}\t${value}\t${key || ''}`);
1189
- }
1190
- const hasSubscribers = Boolean(Cache.get(identifier)?.size);
1191
- if (isCacheOperationValue(value) || hasSubscribers) {
1192
- let buffer = this._buffered.get(identifier);
1193
- if (!buffer) {
1194
- buffer = [];
1195
- this._buffered.set(identifier, buffer);
1196
- }
1197
- buffer.push([value, key]);
1198
- void this._scheduleNotify();
1199
- }
1200
- return hasSubscribers;
1201
- }
1202
- _onNextFlush(cb) {
1203
- this._onFlushCB = cb;
1204
- }
1205
- _scheduleNotify() {
1206
- const asyncFlush = this.store._enableAsyncFlush;
1207
- if (this._hasFlush) {
1208
- if (asyncFlush !== false && !runLoopIsFlushing()) {
1209
- return;
1210
- }
1211
- }
1212
- if (asyncFlush && !runLoopIsFlushing()) {
1213
- this._hasFlush = true;
1214
- return;
1215
- }
1216
- this._flush();
1217
- }
1218
- _flush() {
1219
- if (this._buffered.size) {
1220
- this._buffered.forEach((states, identifier) => {
1221
- states.forEach(args => {
1222
- // @ts-expect-error
1223
- this._flushNotification(identifier, args[0], args[1]);
1224
- });
1225
- });
1226
- this._buffered = new Map();
1227
- }
1228
- this._hasFlush = false;
1229
- this._onFlushCB?.();
1230
- this._onFlushCB = undefined;
1231
- }
1232
- _flushNotification(identifier, value, key) {
1233
- if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
1234
- // eslint-disable-next-line no-console
1235
- console.log(`Notifying: ${String(identifier)}\t${value}\t${key || ''}`);
1236
- }
1237
-
1238
- // TODO for documents this will need to switch based on Identifier kind
1239
- if (isCacheOperationValue(value)) {
1240
- let callbackMap = Cache.get(isDocumentIdentifier(identifier) ? 'document' : 'resource');
1241
- if (callbackMap) {
1242
- callbackMap.forEach(cb => {
1243
- cb(identifier, value);
1244
- });
1245
- }
1246
- }
1247
- let callbackMap = Cache.get(identifier);
1248
- if (!callbackMap || !callbackMap.size) {
1249
- return false;
1250
- }
1251
- callbackMap.forEach(cb => {
1252
- // @ts-expect-error overload doesn't narrow within body
1253
- cb(identifier, value, key);
1254
- });
1255
- return true;
1256
- }
1257
- destroy() {
1258
- this.isDestroyed = true;
1259
- Tokens.clear();
1260
- Cache.clear();
1261
- }
1262
- }
1263
- var _class$1, _descriptor$1;
1068
+ @module @ember-data/store
1069
+ */
1264
1070
  /**
1265
1071
  @module @ember-data/store
1266
1072
  */
@@ -1288,7 +1094,7 @@ let RecordReference = (_class$1 = class RecordReference {
1288
1094
  });
1289
1095
  }
1290
1096
  destroy() {
1291
- unsubscribe(this.___token);
1097
+ this.store.notifications.unsubscribe(this.___token);
1292
1098
  }
1293
1099
  get type() {
1294
1100
  return this.identifier().type;
@@ -2555,6 +2361,203 @@ class CacheManager {
2555
2361
  return _classPrivateFieldBase(this, _cache)[_cache].isDeletionCommitted(identifier);
2556
2362
  }
2557
2363
  }
2364
+ let tokenId = 0;
2365
+ const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
2366
+ function isCacheOperationValue(value) {
2367
+ return CacheOperations.has(value);
2368
+ }
2369
+ function runLoopIsFlushing() {
2370
+ //@ts-expect-error
2371
+ return !!_backburner.currentInstance && _backburner._autorun !== true;
2372
+ }
2373
+ function _unsubscribe(tokens, token, cache) {
2374
+ let identifier = tokens.get(token);
2375
+ if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
2376
+ if (!identifier) {
2377
+ // eslint-disable-next-line no-console
2378
+ console.log('Passed unknown unsubscribe token to unsubscribe', identifier);
2379
+ }
2380
+ }
2381
+ if (identifier) {
2382
+ tokens.delete(token);
2383
+ const map = cache.get(identifier);
2384
+ map?.delete(token);
2385
+ }
2386
+ }
2387
+
2388
+ /**
2389
+ * The NotificationManager provides the ability to subscribe to
2390
+ * changes to Cache state.
2391
+ *
2392
+ * This Feature is what allows EmberData to create subscriptions that
2393
+ * work with any framework or change-notification system.
2394
+ *
2395
+ * @class NotificationManager
2396
+ * @public
2397
+ */
2398
+ class NotificationManager {
2399
+ constructor(store) {
2400
+ this.store = store;
2401
+ this.isDestroyed = false;
2402
+ this._buffered = new Map();
2403
+ this._hasFlush = false;
2404
+ this._cache = new Map();
2405
+ this._tokens = new Map();
2406
+ }
2407
+
2408
+ /**
2409
+ * Subscribe to changes for a given resource identifier, resource addition/removal, or document addition/removal.
2410
+ *
2411
+ * ```ts
2412
+ * export type CacheOperation = 'added' | 'removed' | 'updated' | 'state';
2413
+ *
2414
+ * export interface NotificationCallback {
2415
+ * (identifier: StableRecordIdentifier, notificationType: 'attributes' | 'relationships', key?: string): void;
2416
+ * (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'state'): void;
2417
+ * (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
2418
+ * }
2419
+ * export interface ResourceOperationCallback {
2420
+ * // resource updates
2421
+ * (identifier: StableRecordIdentifier, notificationType: CacheOperation): void;
2422
+ * }
2423
+ * export interface DocumentOperationCallback {
2424
+ * // document updates
2425
+ * (identifier: StableDocumentIdentifier, notificationType: CacheOperation): void;
2426
+ * }
2427
+ * ```
2428
+ *
2429
+ * @method subscribe
2430
+ * @public
2431
+ * @param {StableDocumentIdentifier | StableRecordIdentifier | 'resource' | 'document'} identifier
2432
+ * @param {NotificationCallback | ResourceOperationCallback | DocumentOperationCallback} callback
2433
+ * @returns {UnsubscribeToken} an opaque token to be used with unsubscribe
2434
+ */
2435
+
2436
+ subscribe(identifier, callback) {
2437
+ assert(`Expected to receive a stable Identifier to subscribe to`, identifier === 'resource' || identifier === 'document' || isStableIdentifier(identifier) || isDocumentIdentifier(identifier));
2438
+ let map = this._cache.get(identifier);
2439
+ if (!map) {
2440
+ map = new Map();
2441
+ this._cache.set(identifier, map);
2442
+ }
2443
+ let unsubToken = macroCondition(getOwnConfig().env.DEBUG) ? {
2444
+ _tokenRef: tokenId++
2445
+ } : {};
2446
+ map.set(unsubToken, callback);
2447
+ this._tokens.set(unsubToken, identifier);
2448
+ return unsubToken;
2449
+ }
2450
+
2451
+ /**
2452
+ * remove a previous subscription
2453
+ *
2454
+ * @method unsubscribe
2455
+ * @public
2456
+ * @param {UnsubscribeToken} token
2457
+ */
2458
+ unsubscribe(token) {
2459
+ if (!this.isDestroyed) {
2460
+ _unsubscribe(this._tokens, token, this._cache);
2461
+ }
2462
+ }
2463
+
2464
+ /**
2465
+ * Custom Caches and Application Code should not call this method directly.
2466
+ *
2467
+ * @method notify
2468
+ * @param identifier
2469
+ * @param value
2470
+ * @param key
2471
+ * @return {Boolean} whether a notification was delivered to any subscribers
2472
+ * @private
2473
+ */
2474
+
2475
+ notify(identifier, value, key) {
2476
+ assert(`Notify does not accept a key argument for the namespace '${value}'. Received key '${key || ''}'.`, !key || value === 'attributes' || value === 'relationships');
2477
+ if (!isStableIdentifier(identifier) && !isDocumentIdentifier(identifier)) {
2478
+ if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
2479
+ // eslint-disable-next-line no-console
2480
+ console.log(`Notifying: Expected to receive a stable Identifier to notify '${value}' '${key || ''}' with, but ${String(identifier)} is not in the cache`, identifier);
2481
+ }
2482
+ return false;
2483
+ }
2484
+ if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
2485
+ // eslint-disable-next-line no-console
2486
+ console.log(`Buffering Notify: ${String(identifier.lid)}\t${value}\t${key || ''}`);
2487
+ }
2488
+ const hasSubscribers = Boolean(this._cache.get(identifier)?.size);
2489
+ if (isCacheOperationValue(value) || hasSubscribers) {
2490
+ let buffer = this._buffered.get(identifier);
2491
+ if (!buffer) {
2492
+ buffer = [];
2493
+ this._buffered.set(identifier, buffer);
2494
+ }
2495
+ buffer.push([value, key]);
2496
+ void this._scheduleNotify();
2497
+ }
2498
+ return hasSubscribers;
2499
+ }
2500
+ _onNextFlush(cb) {
2501
+ this._onFlushCB = cb;
2502
+ }
2503
+ _scheduleNotify() {
2504
+ const asyncFlush = this.store._enableAsyncFlush;
2505
+ if (this._hasFlush) {
2506
+ if (asyncFlush !== false && !runLoopIsFlushing()) {
2507
+ return;
2508
+ }
2509
+ }
2510
+ if (asyncFlush && !runLoopIsFlushing()) {
2511
+ this._hasFlush = true;
2512
+ return;
2513
+ }
2514
+ this._flush();
2515
+ }
2516
+ _flush() {
2517
+ if (this._buffered.size) {
2518
+ this._buffered.forEach((states, identifier) => {
2519
+ states.forEach(args => {
2520
+ // @ts-expect-error
2521
+ this._flushNotification(identifier, args[0], args[1]);
2522
+ });
2523
+ });
2524
+ this._buffered = new Map();
2525
+ }
2526
+ this._hasFlush = false;
2527
+ this._onFlushCB?.();
2528
+ this._onFlushCB = undefined;
2529
+ }
2530
+ _flushNotification(identifier, value, key) {
2531
+ if (macroCondition(getOwnConfig().debug.LOG_NOTIFICATIONS)) {
2532
+ // eslint-disable-next-line no-console
2533
+ console.log(`Notifying: ${String(identifier)}\t${value}\t${key || ''}`);
2534
+ }
2535
+
2536
+ // TODO for documents this will need to switch based on Identifier kind
2537
+ if (isCacheOperationValue(value)) {
2538
+ let callbackMap = this._cache.get(isDocumentIdentifier(identifier) ? 'document' : 'resource');
2539
+ if (callbackMap) {
2540
+ callbackMap.forEach(cb => {
2541
+ cb(identifier, value);
2542
+ });
2543
+ }
2544
+ }
2545
+ let callbackMap = this._cache.get(identifier);
2546
+ if (!callbackMap || !callbackMap.size) {
2547
+ return false;
2548
+ }
2549
+ callbackMap.forEach(cb => {
2550
+ // @ts-expect-error overload doesn't narrow within body
2551
+ cb(identifier, value, key);
2552
+ });
2553
+ return true;
2554
+ }
2555
+ destroy() {
2556
+ this.isDestroyed = true;
2557
+ this._tokens.clear();
2558
+ this._cache.clear();
2559
+ }
2560
+ }
2558
2561
  var _class, _descriptor, _class3, _descriptor2;
2559
2562
  const ARRAY_GETTER_METHODS = new Set([Symbol.iterator, 'concat', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'map', 'reduce', 'reduceRight', 'slice', 'some', 'values']);
2560
2563
  const ARRAY_SETTER_METHODS = new Set(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
@@ -2661,13 +2664,13 @@ let IdentifierArray = (_class3 = class IdentifierArray {
2661
2664
  @type Store
2662
2665
  */
2663
2666
 
2664
- destroy() {
2665
- this.isDestroying = true;
2667
+ destroy(clear) {
2668
+ this.isDestroying = !clear;
2666
2669
  // changing the reference breaks the Proxy
2667
2670
  // this[SOURCE] = [];
2668
2671
  this[SOURCE].length = 0;
2669
2672
  this[NOTIFY]();
2670
- this.isDestroyed = true;
2673
+ this.isDestroyed = !clear;
2671
2674
  }
2672
2675
 
2673
2676
  // length must be on self for proxied methods to work properly
@@ -2958,8 +2961,8 @@ class Collection extends IdentifierArray {
2958
2961
  });
2959
2962
  return promise;
2960
2963
  }
2961
- destroy() {
2962
- super.destroy();
2964
+ destroy(clear) {
2965
+ super.destroy(clear);
2963
2966
  this._manager._managed.delete(this);
2964
2967
  this._manager._pending.delete(this);
2965
2968
  }
@@ -2991,7 +2994,6 @@ function extractIdentifierFromRecord$1(record) {
2991
2994
  @module @ember-data/store
2992
2995
  */
2993
2996
 
2994
- const RecordArraysCache = new Map();
2995
2997
  const FAKE_ARR = {};
2996
2998
  const SLICE_BATCH_SIZE = 1200;
2997
2999
  /**
@@ -3057,11 +3059,15 @@ class RecordArrayManager {
3057
3059
  this._pending = new Map();
3058
3060
  this._staged = new Map();
3059
3061
  this._keyedArrays = new Map();
3060
- this._identifiers = RecordArraysCache;
3062
+ this._identifiers = new Map();
3063
+ this._set = new Map();
3064
+ this._visibilitySet = new Map();
3061
3065
  this._subscription = this.store.notifications.subscribe('resource', (identifier, type) => {
3062
3066
  if (type === 'added') {
3067
+ this._visibilitySet.set(identifier, true);
3063
3068
  this.identifierAdded(identifier);
3064
3069
  } else if (type === 'removed') {
3070
+ this._visibilitySet.set(identifier, false);
3065
3071
  this.identifierRemoved(identifier);
3066
3072
  } else if (type === 'state') {
3067
3073
  this.identifierChanged(identifier);
@@ -3073,7 +3079,7 @@ class RecordArrayManager {
3073
3079
  if (!pending || this.isDestroying || this.isDestroyed) {
3074
3080
  return;
3075
3081
  }
3076
- sync(array, pending);
3082
+ sync(array, pending, this._set.get(array));
3077
3083
  this._pending.delete(array);
3078
3084
  }
3079
3085
 
@@ -3106,6 +3112,7 @@ class RecordArrayManager {
3106
3112
  manager: this
3107
3113
  });
3108
3114
  this._live.set(type, array);
3115
+ this._set.set(array, new Set(identifiers));
3109
3116
  }
3110
3117
  return array;
3111
3118
  }
@@ -3123,8 +3130,9 @@ class RecordArrayManager {
3123
3130
  };
3124
3131
  let array = new Collection(options);
3125
3132
  this._managed.add(array);
3133
+ this._set.set(array, new Set(options.identifiers || []));
3126
3134
  if (config.identifiers) {
3127
- associate(array, config.identifiers);
3135
+ associate(this._identifiers, array, config.identifiers);
3128
3136
  }
3129
3137
  return array;
3130
3138
  }
@@ -3148,7 +3156,7 @@ class RecordArrayManager {
3148
3156
  const allPending = this._pending;
3149
3157
  let pending = new Map();
3150
3158
  if (includeManaged) {
3151
- let managed = RecordArraysCache.get(identifier);
3159
+ let managed = this._identifiers.get(identifier);
3152
3160
  if (managed) {
3153
3161
  managed.forEach(arr => {
3154
3162
  let changes = allPending.get(arr);
@@ -3194,12 +3202,13 @@ class RecordArrayManager {
3194
3202
  const old = source.slice();
3195
3203
  source.length = 0;
3196
3204
  fastPush(source, identifiers);
3205
+ this._set.set(array, new Set(identifiers));
3197
3206
  notifyArray(array);
3198
3207
  array.meta = payload.meta || null;
3199
3208
  array.links = payload.links || null;
3200
3209
  array.isLoaded = true;
3201
- disassociate(array, old);
3202
- associate(array, identifiers);
3210
+ disassociate(this._identifiers, array, old);
3211
+ associate(this._identifiers, array, identifiers);
3203
3212
  }
3204
3213
  identifierAdded(identifier) {
3205
3214
  let changeSets = this._getPendingFor(identifier, false);
@@ -3231,62 +3240,72 @@ class RecordArrayManager {
3231
3240
  }
3232
3241
  identifierChanged(identifier) {
3233
3242
  let newState = this.store._instanceCache.recordIsLoaded(identifier, true);
3243
+
3244
+ // if the change matches the most recent direct added/removed
3245
+ // state, then we can ignore it
3246
+ if (this._visibilitySet.get(identifier) === newState) {
3247
+ return;
3248
+ }
3234
3249
  if (newState) {
3235
3250
  this.identifierAdded(identifier);
3236
3251
  } else {
3237
3252
  this.identifierRemoved(identifier);
3238
3253
  }
3239
3254
  }
3240
- clear() {
3241
- this._live.forEach(array => array.destroy());
3242
- this._managed.forEach(array => array.destroy());
3255
+ clear(isClear = true) {
3256
+ this._live.forEach(array => array.destroy(isClear));
3257
+ this._managed.forEach(array => array.destroy(isClear));
3243
3258
  this._managed.clear();
3244
- RecordArraysCache.clear();
3259
+ this._identifiers.clear();
3260
+ this._pending.clear();
3261
+ this._set.forEach(set => set.clear());
3262
+ this._visibilitySet.clear();
3245
3263
  }
3246
3264
  destroy() {
3247
3265
  this.isDestroying = true;
3248
- this.clear();
3266
+ this.clear(false);
3249
3267
  this._live.clear();
3250
3268
  this.isDestroyed = true;
3251
3269
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
3252
3270
  this.store.notifications.unsubscribe(this._subscription);
3253
3271
  }
3254
3272
  }
3255
- function associate(array, identifiers) {
3273
+ function associate(ArraysCache, array, identifiers) {
3256
3274
  for (let i = 0; i < identifiers.length; i++) {
3257
3275
  let identifier = identifiers[i];
3258
- let cache = RecordArraysCache.get(identifier);
3276
+ let cache = ArraysCache.get(identifier);
3259
3277
  if (!cache) {
3260
3278
  cache = new Set();
3261
- RecordArraysCache.set(identifier, cache);
3279
+ ArraysCache.set(identifier, cache);
3262
3280
  }
3263
3281
  cache.add(array);
3264
3282
  }
3265
3283
  }
3266
- function disassociate(array, identifiers) {
3284
+ function disassociate(ArraysCache, array, identifiers) {
3267
3285
  for (let i = 0; i < identifiers.length; i++) {
3268
- disassociateIdentifier(array, identifiers[i]);
3286
+ disassociateIdentifier(ArraysCache, array, identifiers[i]);
3269
3287
  }
3270
3288
  }
3271
- function disassociateIdentifier(array, identifier) {
3272
- let cache = RecordArraysCache.get(identifier);
3289
+ function disassociateIdentifier(ArraysCache, array, identifier) {
3290
+ let cache = ArraysCache.get(identifier);
3273
3291
  if (cache) {
3274
3292
  cache.delete(array);
3275
3293
  }
3276
3294
  }
3277
- function sync(array, changes) {
3295
+ function sync(array, changes, arraySet) {
3278
3296
  let state = array[SOURCE];
3279
3297
  const adds = [];
3280
3298
  const removes = [];
3281
3299
  changes.forEach((value, key) => {
3282
3300
  if (value === 'add') {
3283
3301
  // likely we want to keep a Set along-side
3284
- if (state.includes(key)) {
3302
+ if (arraySet.has(key)) {
3285
3303
  return;
3286
3304
  }
3287
3305
  adds.push(key);
3306
+ arraySet.add(key);
3288
3307
  } else {
3289
- if (state.includes(key)) {
3308
+ if (arraySet.has(key)) {
3290
3309
  removes.push(key);
3291
3310
  }
3292
3311
  }
@@ -3294,6 +3313,7 @@ function sync(array, changes) {
3294
3313
  if (removes.length) {
3295
3314
  if (removes.length === state.length) {
3296
3315
  state.length = 0;
3316
+ arraySet.clear();
3297
3317
  // changing the reference breaks the Proxy
3298
3318
  // state = array[SOURCE] = [];
3299
3319
  } else {
@@ -3301,6 +3321,7 @@ function sync(array, changes) {
3301
3321
  const index = state.indexOf(i);
3302
3322
  if (index !== -1) {
3303
3323
  state.splice(index, 1);
3324
+ arraySet.delete(i);
3304
3325
  }
3305
3326
  });
3306
3327
  }
@@ -3720,17 +3741,34 @@ class Store extends EmberObject {
3720
3741
  _run(cb) {
3721
3742
  assert(`EmberData should never encounter a nested run`, !this._cbs);
3722
3743
  const _cbs = this._cbs = {};
3723
- cb();
3724
- if (_cbs.coalesce) {
3725
- _cbs.coalesce();
3726
- }
3727
- if (_cbs.sync) {
3728
- _cbs.sync();
3729
- }
3730
- if (_cbs.notify) {
3731
- _cbs.notify();
3744
+ if (macroCondition(getOwnConfig().env.DEBUG)) {
3745
+ try {
3746
+ cb();
3747
+ if (_cbs.coalesce) {
3748
+ _cbs.coalesce();
3749
+ }
3750
+ if (_cbs.sync) {
3751
+ _cbs.sync();
3752
+ }
3753
+ if (_cbs.notify) {
3754
+ _cbs.notify();
3755
+ }
3756
+ } finally {
3757
+ this._cbs = null;
3758
+ }
3759
+ } else {
3760
+ cb();
3761
+ if (_cbs.coalesce) {
3762
+ _cbs.coalesce();
3763
+ }
3764
+ if (_cbs.sync) {
3765
+ _cbs.sync();
3766
+ }
3767
+ if (_cbs.notify) {
3768
+ _cbs.notify();
3769
+ }
3770
+ this._cbs = null;
3732
3771
  }
3733
- this._cbs = null;
3734
3772
  }
3735
3773
  _join(cb) {
3736
3774
  if (this._cbs) {
@@ -5017,12 +5055,11 @@ class Store extends EmberObject {
5017
5055
  // during unload
5018
5056
  if (macroCondition(getOwnConfig().packages.HAS_GRAPH_PACKAGE)) {
5019
5057
  const peekGraph = importSync('@ember-data/graph/-private').peekGraph;
5020
- let graph = peekGraph(this);
5058
+ const graph = peekGraph(this);
5021
5059
  if (graph) {
5022
5060
  graph.identifiers.clear();
5023
5061
  }
5024
5062
  }
5025
- this.notifications.destroy();
5026
5063
  this.recordArrayManager.clear();
5027
5064
  this._instanceCache.clear();
5028
5065
  } else {
@@ -5538,6 +5575,7 @@ class Store extends EmberObject {
5538
5575
  graph.destroy();
5539
5576
  }
5540
5577
  }
5578
+ this.notifications.destroy();
5541
5579
  this.recordArrayManager.destroy();
5542
5580
  this.identifierCache.destroy();
5543
5581
  this.unloadAll();