@ember-data/store 4.6.1 → 4.7.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 (45) hide show
  1. package/addon/-debug/index.js +35 -13
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
  3. package/addon/-private/caches/instance-cache.ts +690 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
  5. package/addon/-private/index.ts +44 -24
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
  9. package/addon/-private/managers/record-array-manager.ts +377 -0
  10. package/addon/-private/managers/record-data-manager.ts +845 -0
  11. package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
  12. package/addon/-private/managers/record-notification-manager.ts +109 -0
  13. package/addon/-private/network/fetch-manager.ts +567 -0
  14. package/addon/-private/{finders.js → network/finders.js} +14 -17
  15. package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
  16. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
  17. package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
  18. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
  19. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  20. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  21. package/addon/-private/{core-store.ts → store-service.ts} +574 -215
  22. package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
  23. package/addon/-private/{common.js → utils/common.js} +1 -2
  24. package/addon/-private/utils/construct-resource.ts +2 -2
  25. package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
  26. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  27. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
  28. package/addon/-private/utils/promise-record.ts +5 -6
  29. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  30. package/addon/-private/utils/uuid-polyfill.ts +73 -0
  31. package/package.json +12 -8
  32. package/addon/-private/backburner.js +0 -25
  33. package/addon/-private/errors-utils.js +0 -146
  34. package/addon/-private/fetch-manager.ts +0 -597
  35. package/addon/-private/identity-map.ts +0 -54
  36. package/addon/-private/instance-cache.ts +0 -387
  37. package/addon/-private/internal-model-factory.ts +0 -359
  38. package/addon/-private/internal-model-map.ts +0 -121
  39. package/addon/-private/model/internal-model.ts +0 -602
  40. package/addon/-private/record-array-manager.ts +0 -444
  41. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
  42. package/addon/-private/record-arrays/record-array.ts +0 -318
  43. package/addon/-private/record-data-store-wrapper.ts +0 -243
  44. package/addon/-private/record-notification-manager.ts +0 -73
  45. package/addon/-private/weak-cache.ts +0 -125
@@ -1,20 +1,15 @@
1
1
  import { assert } from '@ember/debug';
2
- import { DEBUG } from '@glimmer/env';
3
2
 
4
3
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
5
4
  import type { RecordData } from '@ember-data/types/q/record-data';
6
5
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
7
6
 
8
- import WeakCache from './weak-cache';
9
-
10
7
  /*
11
8
  * Returns the RecordData instance associated with a given
12
9
  * Model or Identifier
13
10
  */
14
11
 
15
- const RecordDataForIdentifierCache = new WeakCache<StableRecordIdentifier | RecordInstance, RecordData>(
16
- DEBUG ? 'recordData' : ''
17
- );
12
+ const RecordDataForIdentifierCache = new Map<StableRecordIdentifier | RecordInstance, RecordData>();
18
13
 
19
14
  export function setRecordDataFor(identifier: StableRecordIdentifier | RecordInstance, recordData: RecordData): void {
20
15
  assert(
@@ -24,7 +19,7 @@ export function setRecordDataFor(identifier: StableRecordIdentifier | RecordInst
24
19
  RecordDataForIdentifierCache.set(identifier, recordData);
25
20
  }
26
21
 
27
- export function removeRecordDataFor(identifier: StableRecordIdentifier): void {
22
+ export function removeRecordDataFor(identifier: StableRecordIdentifier | RecordInstance): void {
28
23
  RecordDataForIdentifierCache.delete(identifier);
29
24
  }
30
25
 
@@ -2,38 +2,58 @@
2
2
  @module @ember-data/store
3
3
  */
4
4
 
5
- export { default as Store, storeFor } from './core-store';
5
+ import { assert, deprecate } from '@ember/debug';
6
6
 
7
- export { recordIdentifierFor } from './internal-model-factory';
7
+ import { DEPRECATE_HELPERS } from '@ember-data/private-build-infra/deprecations';
8
8
 
9
- export { default as Snapshot } from './snapshot';
9
+ import _normalize from './utils/normalize-model-name';
10
+
11
+ export { default as Store, storeFor } from './store-service';
12
+
13
+ export { recordIdentifierFor } from './caches/instance-cache';
14
+
15
+ export { default as Snapshot } from './network/snapshot';
10
16
  export {
11
17
  setIdentifierGenerationMethod,
12
18
  setIdentifierUpdateMethod,
13
19
  setIdentifierForgetMethod,
14
20
  setIdentifierResetMethod,
15
- } from './identifier-cache';
16
-
17
- export { default as normalizeModelName } from './normalize-model-name';
18
- export { default as coerceId } from './coerce-id';
19
-
20
- export { errorsHashToArray, errorsArrayToHash } from './errors-utils';
21
+ isStableIdentifier,
22
+ } from './caches/identifier-cache';
23
+
24
+ export function normalizeModelName(modelName: string) {
25
+ if (DEPRECATE_HELPERS) {
26
+ deprecate(
27
+ `the helper function normalizeModelName is deprecated. You should use model names that are already normalized, or use string helpers of your own. This function is primarily an alias for dasherize from @ember/string.`,
28
+ false,
29
+ {
30
+ id: 'ember-data:deprecate-normalize-modelname-helper',
31
+ for: 'ember-data',
32
+ until: '5.0',
33
+ since: { available: '4.8', enabled: '4.8' },
34
+ }
35
+ );
36
+ return _normalize(modelName);
37
+ }
38
+ assert(`normalizeModelName support has been removed`);
39
+ }
40
+
41
+ // TODO this should be a deprecated helper but we have so much usage of it
42
+ // to also eliminate
43
+ export { default as coerceId } from './utils/coerce-id';
21
44
 
22
- // `ember-data-model-fragments` relies on `InternalModel`
23
- export { default as InternalModel } from './model/internal-model';
24
-
25
- export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './promise-proxies';
26
-
27
- export { default as RecordArray } from './record-arrays/record-array';
28
- export { default as AdapterPopulatedRecordArray } from './record-arrays/adapter-populated-record-array';
29
-
30
- export { default as RecordArrayManager } from './record-array-manager';
45
+ export {
46
+ default as RecordArray,
47
+ default as IdentifierArray,
48
+ Collection as AdapterPopulatedRecordArray,
49
+ SOURCE,
50
+ MUTATE,
51
+ IDENTIFIER_ARRAY_TAG,
52
+ } from './record-arrays/identifier-array';
53
+ export { default as RecordArrayManager, fastPush } from './managers/record-array-manager';
31
54
 
32
55
  // // Used by tests
33
- export { default as SnapshotRecordArray } from './snapshot-record-array';
34
-
35
- // New
36
- export { default as recordDataFor, removeRecordDataFor } from './record-data-for';
37
- export { default as RecordDataStoreWrapper } from './record-data-store-wrapper';
56
+ export { default as SnapshotRecordArray } from './network/snapshot-record-array';
38
57
 
39
- export { default as WeakCache } from './weak-cache';
58
+ // leaked for private use / test use, should investigate removing
59
+ export { default as recordDataFor, removeRecordDataFor } from './caches/record-data-for';
@@ -10,9 +10,9 @@ import type { SingleResourceDocument } from '@ember-data/types/q/ember-data-json
10
10
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
11
11
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
12
12
 
13
- import type Store from '../core-store';
14
- import type { NotificationType } from '../record-notification-manager';
15
- import { unsubscribe } from '../record-notification-manager';
13
+ import type { NotificationType } from '../managers/record-notification-manager';
14
+ import { unsubscribe } from '../managers/record-notification-manager';
15
+ import type Store from '../store-service';
16
16
 
17
17
  /**
18
18
  @module @ember-data/store
@@ -27,18 +27,20 @@ import { unsubscribe } from '../record-notification-manager';
27
27
  @extends Reference
28
28
  */
29
29
  export default class RecordReference {
30
+ declare store: Store;
30
31
  // unsubscribe token given to us by the notification manager
31
- #token!: Object;
32
- #identifier: StableRecordIdentifier;
32
+ ___token!: Object;
33
+ ___identifier: StableRecordIdentifier;
33
34
 
34
35
  @tracked _ref = 0;
35
36
 
36
- constructor(public store: Store, identifier: StableRecordIdentifier) {
37
- this.#identifier = identifier;
38
- this.#token = store._notificationManager.subscribe(
37
+ constructor(store: Store, identifier: StableRecordIdentifier) {
38
+ this.store = store;
39
+ this.___identifier = identifier;
40
+ this.___token = store._notificationManager.subscribe(
39
41
  identifier,
40
42
  (_: StableRecordIdentifier, bucket: NotificationType, notifiedKey?: string) => {
41
- if (bucket === 'identity' || ((bucket === 'attributes' || bucket === 'property') && notifiedKey === 'id')) {
43
+ if (bucket === 'identity' || (bucket === 'attributes' && notifiedKey === 'id')) {
42
44
  this._ref++;
43
45
  }
44
46
  }
@@ -46,7 +48,7 @@ export default class RecordReference {
46
48
  }
47
49
 
48
50
  destroy() {
49
- unsubscribe(this.#token);
51
+ unsubscribe(this.___token);
50
52
  }
51
53
 
52
54
  get type(): string {
@@ -73,7 +75,7 @@ export default class RecordReference {
73
75
  */
74
76
  id() {
75
77
  this._ref; // consume the tracked prop
76
- return this.#identifier.id;
78
+ return this.___identifier.id;
77
79
  }
78
80
 
79
81
  /**
@@ -95,7 +97,7 @@ export default class RecordReference {
95
97
  @return {String} The identifier of the record.
96
98
  */
97
99
  identifier(): StableRecordIdentifier {
98
- return this.#identifier;
100
+ return this.___identifier;
99
101
  }
100
102
 
101
103
  /**
@@ -183,7 +185,7 @@ export default class RecordReference {
183
185
  @return {Model} the record for this RecordReference
184
186
  */
185
187
  value(): RecordInstance | null {
186
- return this.store.peekRecord(this.#identifier);
188
+ return this.store.peekRecord(this.___identifier);
187
189
  }
188
190
 
189
191
  /**
@@ -1,6 +1,5 @@
1
1
  import { getOwner } from '@ember/application';
2
2
  import { deprecate } from '@ember/debug';
3
- import { get } from '@ember/object';
4
3
 
5
4
  import { importSync } from '@embroider/macros';
6
5
 
@@ -10,8 +9,8 @@ import { DEPRECATE_STRING_ARG_SCHEMAS } from '@ember-data/private-build-infra/de
10
9
  import type { RecordIdentifier } from '@ember-data/types/q/identifier';
11
10
  import type { AttributesSchema, RelationshipsSchema } from '@ember-data/types/q/record-data-schemas';
12
11
 
13
- import type Store from './core-store';
14
- import normalizeModelName from './normalize-model-name';
12
+ import type Store from '../store-service';
13
+ import normalizeModelName from '../utils/normalize-model-name';
15
14
 
16
15
  type ModelForMixin = (store: Store, normalizedModelName: string) => Model | null;
17
16
 
@@ -27,10 +26,15 @@ if (HAS_MODEL_PACKAGE) {
27
26
  }
28
27
 
29
28
  export class DSModelSchemaDefinitionService {
30
- private _relationshipsDefCache = Object.create(null);
31
- private _attributesDefCache = Object.create(null);
32
-
33
- constructor(public store: Store) {}
29
+ declare store: Store;
30
+ declare _relationshipsDefCache;
31
+ declare _attributesDefCache;
32
+
33
+ constructor(store: Store) {
34
+ this.store = store;
35
+ this._relationshipsDefCache = Object.create(null);
36
+ this._attributesDefCache = Object.create(null);
37
+ }
34
38
 
35
39
  // Following the existing RD implementation
36
40
  attributesDefinitionFor(identifier: RecordIdentifier | { type: string }): AttributesSchema {
@@ -55,7 +59,7 @@ export class DSModelSchemaDefinitionService {
55
59
 
56
60
  if (attributes === undefined) {
57
61
  let modelClass = this.store.modelFor(modelName);
58
- let attributeMap = get(modelClass, 'attributes');
62
+ let attributeMap = modelClass.attributes;
59
63
 
60
64
  attributes = Object.create(null);
61
65
  attributeMap.forEach((meta, name) => (attributes[name] = meta));
@@ -88,7 +92,7 @@ export class DSModelSchemaDefinitionService {
88
92
 
89
93
  if (relationships === undefined) {
90
94
  let modelClass = this.store.modelFor(modelName);
91
- relationships = get(modelClass, 'relationshipsObject') || null;
95
+ relationships = modelClass.relationshipsObject || null;
92
96
  this._relationshipsDefCache[modelName] = relationships;
93
97
  }
94
98
 
@@ -1,18 +1,21 @@
1
- import { DEBUG } from '@glimmer/env';
2
-
3
1
  import type { ModelSchema } from '@ember-data/types/q/ds-model';
4
2
  import type { AttributeSchema, RelationshipSchema } from '@ember-data/types/q/record-data-schemas';
5
3
  import type { Dict } from '@ember-data/types/q/utils';
6
4
 
7
- import type Store from '../core-store';
8
- import WeakCache from '../weak-cache';
5
+ import type Store from '../store-service';
6
+
7
+ // if modelFor turns out to be a bottleneck we should replace with a Map
8
+ // and clear it during store teardown.
9
+ const AvailableShims = new WeakMap<Store, Dict<ShimModelClass>>();
9
10
 
10
- const AvailableShims = new WeakCache<Store, Dict<ShimModelClass>>(DEBUG ? 'schema-shims' : '');
11
- AvailableShims._generator = () => {
12
- return Object.create(null) as Dict<ShimModelClass>;
13
- };
14
11
  export function getShimClass(store: Store, modelName: string): ShimModelClass {
15
- let shims = AvailableShims.lookup(store);
12
+ let shims = AvailableShims.get(store);
13
+
14
+ if (!shims) {
15
+ shims = Object.create(null) as Dict<ShimModelClass>;
16
+ AvailableShims.set(store, shims);
17
+ }
18
+
16
19
  let shim = shims[modelName];
17
20
  if (shim === undefined) {
18
21
  shim = shims[modelName] = new ShimModelClass(store, modelName);
@@ -33,8 +36,12 @@ function mapFromHash<T>(hash: Dict<T>): Map<string, T> {
33
36
 
34
37
  // Mimics the static apis of DSModel
35
38
  export default class ShimModelClass implements ModelSchema {
36
- // TODO Maybe expose the class here?
37
- constructor(private __store: Store, public modelName: string) {}
39
+ declare __store: Store;
40
+ declare modelName: string;
41
+ constructor(store: Store, modelName: string) {
42
+ this.__store = store;
43
+ this.modelName = modelName;
44
+ }
38
45
 
39
46
  get fields(): Map<string, 'attribute' | 'belongsTo' | 'hasMany'> {
40
47
  let attrs = this.__store.getSchemaDefinitionService().attributesDefinitionFor({ type: this.modelName });
@@ -0,0 +1,377 @@
1
+ /**
2
+ @module @ember-data/store
3
+ */
4
+ import type { CollectionResourceDocument } from '@ember-data/types/q/ember-data-json-api';
5
+ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
6
+ import type { Dict } from '@ember-data/types/q/utils';
7
+
8
+ import IdentifierArray, {
9
+ Collection,
10
+ CollectionCreateOptions,
11
+ IDENTIFIER_ARRAY_TAG,
12
+ SOURCE,
13
+ } from '../record-arrays/identifier-array';
14
+ import type Store from '../store-service';
15
+
16
+ const RecordArraysCache = new Map<StableRecordIdentifier, Set<Collection>>();
17
+ const FAKE_ARR = {};
18
+
19
+ const SLICE_BATCH_SIZE = 1200;
20
+ /**
21
+ * This is a clever optimization.
22
+ *
23
+ * clever optimizations rarely stand the test of time, so if you're
24
+ * ever curious or think something better is possible please benchmark
25
+ * and discuss. The benchmark for this at the time of writing is in
26
+ * `scripts/benchmark-push.js`
27
+ *
28
+ * This approach turns out to be 150x faster in Chrome and node than
29
+ * simply using push or concat. It's highly susceptible to the specifics
30
+ * of the batch size, and may require tuning.
31
+ *
32
+ * Clever optimizations should always come with a `why`. This optimization
33
+ * exists for two reasons.
34
+ *
35
+ * 1) array.push(...objects) and Array.prototype.push.apply(arr, objects)
36
+ * are susceptible to stack overflows. The size of objects at which this
37
+ * occurs varies by environment, browser, and current stack depth and memory
38
+ * pressure; however, it occurs in all browsers in fairly pristine conditions
39
+ * somewhere around 125k to 200k elements. Since EmberData regularly encounters
40
+ * arrays larger than this in size, we cannot use push.
41
+ *
42
+ * 2) `array.concat` or simply setting the array to a new reference is often an
43
+ * easier approach; however, native Proxy to an array cannot swap it's target array
44
+ * and attempts at juggling multiple array sources have proven to be victim to a number
45
+ * of browser implementation bugs. Should these bugs be addressed then we could
46
+ * simplify to using `concat`, however, do note this is currently 150x faster
47
+ * than concat, and due to the overloaded signature of concat will likely always
48
+ * be faster.
49
+ *
50
+ * Sincerely,
51
+ * - runspired (Chris Thoburn) 08/21/2022
52
+ *
53
+ * @function fastPush
54
+ * @internal
55
+ * @param target the array to push into
56
+ * @param source the items to push into target
57
+ */
58
+ export function fastPush<T>(target: T[], source: T[]) {
59
+ let startLength = 0;
60
+ let newLength = source.length;
61
+ while (newLength - startLength > SLICE_BATCH_SIZE) {
62
+ // eslint-disable-next-line prefer-spread
63
+ target.push.apply(target, source.slice(startLength, SLICE_BATCH_SIZE));
64
+ startLength += SLICE_BATCH_SIZE;
65
+ }
66
+ // eslint-disable-next-line prefer-spread
67
+ target.push.apply(target, source.slice(startLength));
68
+ }
69
+
70
+ type ChangeSet = Map<StableRecordIdentifier, 'add' | 'del'>;
71
+
72
+ /**
73
+ @class RecordArrayManager
74
+ @internal
75
+ */
76
+ class RecordArrayManager {
77
+ declare store: Store;
78
+ declare isDestroying: boolean;
79
+ declare isDestroyed: boolean;
80
+ declare _live: Map<string, IdentifierArray>;
81
+ declare _managed: Set<IdentifierArray>;
82
+ declare _pending: Map<IdentifierArray, ChangeSet>;
83
+ declare _identifiers: Map<StableRecordIdentifier, Set<Collection>>;
84
+ declare _staged: Map<string, ChangeSet>;
85
+
86
+ constructor(options: { store: Store }) {
87
+ this.store = options.store;
88
+ this.isDestroying = false;
89
+ this.isDestroyed = false;
90
+ this._live = new Map();
91
+ this._managed = new Set();
92
+ this._pending = new Map();
93
+ this._staged = new Map();
94
+ this._identifiers = RecordArraysCache;
95
+ }
96
+
97
+ _syncArray(array: IdentifierArray) {
98
+ const pending = this._pending.get(array);
99
+
100
+ if (!pending || this.isDestroying || this.isDestroyed) {
101
+ return;
102
+ }
103
+
104
+ sync(array, pending);
105
+ this._pending.delete(array);
106
+ }
107
+
108
+ /**
109
+ Get the `RecordArray` for a modelName, which contains all loaded records of
110
+ given modelName.
111
+
112
+ @method liveArrayFor
113
+ @internal
114
+ @param {String} modelName
115
+ @return {RecordArray}
116
+ */
117
+ liveArrayFor(type: string): IdentifierArray {
118
+ let array = this._live.get(type);
119
+ let identifiers: StableRecordIdentifier[] = [];
120
+ let staged = this._staged.get(type);
121
+ if (staged) {
122
+ staged.forEach((value, key) => {
123
+ if (value === 'add') {
124
+ identifiers.push(key);
125
+ }
126
+ });
127
+ this._staged.delete(type);
128
+ }
129
+
130
+ if (!array) {
131
+ array = new IdentifierArray({
132
+ type,
133
+ identifiers,
134
+ store: this.store,
135
+ allowMutation: false,
136
+ manager: this,
137
+ });
138
+ this._live.set(type, array);
139
+ }
140
+
141
+ return array;
142
+ }
143
+
144
+ createArray(config: {
145
+ type: string;
146
+ query?: Dict<unknown>;
147
+ identifiers?: StableRecordIdentifier[];
148
+ doc?: CollectionResourceDocument;
149
+ }): Collection {
150
+ let options: CollectionCreateOptions = {
151
+ type: config.type,
152
+ links: config.doc?.links || null,
153
+ meta: config.doc?.meta || null,
154
+ query: config.query || null,
155
+ identifiers: config.identifiers || [],
156
+ isLoaded: !!config.identifiers?.length,
157
+ allowMutation: false,
158
+ store: this.store,
159
+ manager: this,
160
+ };
161
+ let array = new Collection(options);
162
+ this._managed.add(array);
163
+ if (config.identifiers) {
164
+ associate(array, config.identifiers);
165
+ }
166
+
167
+ return array;
168
+ }
169
+
170
+ dirtyArray(array: IdentifierArray): void {
171
+ if (array === FAKE_ARR) {
172
+ return;
173
+ }
174
+ let tag = array[IDENTIFIER_ARRAY_TAG];
175
+ if (!tag.shouldReset) {
176
+ tag.shouldReset = true;
177
+ tag.ref = null;
178
+ }
179
+ }
180
+
181
+ _getPendingFor(
182
+ identifier: StableRecordIdentifier,
183
+ includeManaged: boolean,
184
+ isRemove?: boolean
185
+ ): Map<IdentifierArray, ChangeSet> | void {
186
+ if (this.isDestroying || this.isDestroyed) {
187
+ return;
188
+ }
189
+
190
+ let liveArray = this._live.get(identifier.type);
191
+ const allPending = this._pending;
192
+ let pending: Map<IdentifierArray, ChangeSet> = new Map();
193
+
194
+ if (includeManaged) {
195
+ let managed = RecordArraysCache.get(identifier);
196
+ if (managed) {
197
+ managed.forEach((arr) => {
198
+ let changes = allPending.get(arr);
199
+ if (!changes) {
200
+ changes = new Map();
201
+ allPending.set(arr, changes);
202
+ }
203
+ pending.set(arr, changes);
204
+ });
205
+ }
206
+ }
207
+
208
+ // during unloadAll we can ignore removes since we've already
209
+ // cleared the array.
210
+ if (liveArray && liveArray[SOURCE].length === 0 && isRemove) {
211
+ return pending;
212
+ }
213
+
214
+ if (!liveArray) {
215
+ // start building a changeset for when we eventually
216
+ // do have a live array
217
+ let changes = this._staged.get(identifier.type);
218
+ if (!changes) {
219
+ changes = new Map();
220
+ this._staged.set(identifier.type, changes);
221
+ }
222
+ pending.set(FAKE_ARR as IdentifierArray, changes);
223
+ } else {
224
+ let changes = allPending.get(liveArray);
225
+ if (!changes) {
226
+ changes = new Map();
227
+ allPending.set(liveArray, changes);
228
+ }
229
+ pending.set(liveArray, changes);
230
+ }
231
+
232
+ return pending;
233
+ }
234
+
235
+ populateManagedArray(array: Collection, identifiers: StableRecordIdentifier[], payload: CollectionResourceDocument) {
236
+ this._pending.delete(array);
237
+ const source = array[SOURCE];
238
+ const old = source.slice();
239
+ source.length = 0;
240
+ fastPush(source, identifiers);
241
+ array[IDENTIFIER_ARRAY_TAG].ref = null;
242
+ array.meta = payload.meta || null;
243
+ array.links = payload.links || null;
244
+ array.isLoaded = true;
245
+
246
+ disassociate(array, old);
247
+ associate(array, identifiers);
248
+ }
249
+
250
+ identifierAdded(identifier: StableRecordIdentifier): void {
251
+ let changeSets = this._getPendingFor(identifier, false);
252
+ if (changeSets) {
253
+ changeSets.forEach((changes, array) => {
254
+ let existing = changes.get(identifier);
255
+ if (existing === 'del') {
256
+ changes.delete(identifier);
257
+ } else {
258
+ changes.set(identifier, 'add');
259
+
260
+ if (changes.size === 1) {
261
+ this.dirtyArray(array);
262
+ }
263
+ }
264
+ });
265
+ }
266
+ }
267
+
268
+ identifierRemoved(identifier: StableRecordIdentifier): void {
269
+ let changeSets = this._getPendingFor(identifier, true, true);
270
+ if (changeSets) {
271
+ changeSets.forEach((changes, array) => {
272
+ let existing = changes.get(identifier);
273
+ if (existing === 'add') {
274
+ changes.delete(identifier);
275
+ } else {
276
+ changes.set(identifier, 'del');
277
+
278
+ if (changes.size === 1) {
279
+ this.dirtyArray(array);
280
+ }
281
+ }
282
+ });
283
+ }
284
+ }
285
+
286
+ identifierChanged(identifier: StableRecordIdentifier): void {
287
+ let newState = this.store._instanceCache.recordIsLoaded(identifier, true);
288
+
289
+ if (newState) {
290
+ this.identifierAdded(identifier);
291
+ } else {
292
+ this.identifierRemoved(identifier);
293
+ }
294
+ }
295
+
296
+ clear() {
297
+ this._live.forEach((array) => array.destroy());
298
+ this._managed.forEach((array) => array.destroy());
299
+ this._managed.clear();
300
+ RecordArraysCache.clear();
301
+ }
302
+
303
+ destroy() {
304
+ this.isDestroying = true;
305
+ this.clear();
306
+ this._live.clear();
307
+ this.isDestroyed = true;
308
+ }
309
+ }
310
+
311
+ function associate(array: Collection, identifiers: StableRecordIdentifier[]) {
312
+ for (let i = 0; i < identifiers.length; i++) {
313
+ let identifier = identifiers[i];
314
+ let cache = RecordArraysCache.get(identifier);
315
+ if (!cache) {
316
+ cache = new Set();
317
+ RecordArraysCache.set(identifier, cache);
318
+ }
319
+ cache.add(array);
320
+ }
321
+ }
322
+
323
+ function disassociate(array: Collection, identifiers: StableRecordIdentifier[]) {
324
+ for (let i = 0; i < identifiers.length; i++) {
325
+ disassociateIdentifier(array, identifiers[i]);
326
+ }
327
+ }
328
+
329
+ export function disassociateIdentifier(array: Collection, identifier: StableRecordIdentifier) {
330
+ let cache = RecordArraysCache.get(identifier);
331
+ if (cache) {
332
+ cache.delete(array);
333
+ }
334
+ }
335
+
336
+ function sync(array: IdentifierArray, changes: Map<StableRecordIdentifier, 'add' | 'del'>) {
337
+ let state = array[SOURCE];
338
+ const adds: StableRecordIdentifier[] = [];
339
+ const removes: StableRecordIdentifier[] = [];
340
+ changes.forEach((value, key) => {
341
+ if (value === 'add') {
342
+ // likely we want to keep a Set along-side
343
+ if (state.includes(key)) {
344
+ return;
345
+ }
346
+ adds.push(key);
347
+ } else {
348
+ removes.push(key);
349
+ }
350
+ });
351
+ if (removes.length) {
352
+ if (removes.length === state.length) {
353
+ state.length = 0;
354
+ // changing the reference breaks the Proxy
355
+ // state = array[SOURCE] = [];
356
+ } else {
357
+ removes.forEach((i) => {
358
+ state.splice(state.indexOf(i), 1);
359
+ });
360
+ }
361
+ }
362
+
363
+ if (adds.length) {
364
+ fastPush(state, adds);
365
+ // changing the reference breaks the Proxy
366
+ // else we could do this
367
+ /*
368
+ if (state.length === 0) {
369
+ array[SOURCE] = adds;
370
+ } else {
371
+ array[SOURCE] = state.concat(adds);
372
+ }
373
+ */
374
+ }
375
+ }
376
+
377
+ export default RecordArrayManager;