@ember-data/store 4.8.0-alpha.4 → 4.8.0-beta.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/-debug/index.js +34 -12
- package/addon/-private/caches/identifier-cache.ts +44 -36
- package/addon/-private/caches/instance-cache.ts +112 -124
- package/addon/-private/caches/record-data-for.ts +1 -6
- package/addon/-private/index.ts +10 -7
- package/addon/-private/legacy-model-support/shim-model-class.ts +11 -8
- package/addon/-private/managers/record-array-manager.ts +282 -326
- package/addon/-private/managers/record-data-manager.ts +143 -128
- package/addon/-private/managers/record-data-store-wrapper.ts +18 -10
- package/addon/-private/managers/record-notification-manager.ts +31 -12
- package/addon/-private/network/fetch-manager.ts +18 -3
- package/addon/-private/network/finders.js +11 -6
- package/addon/-private/network/request-cache.ts +20 -17
- package/addon/-private/network/snapshot-record-array.ts +12 -29
- package/addon/-private/proxies/promise-proxies.ts +72 -11
- package/addon/-private/record-arrays/identifier-array.ts +924 -0
- package/addon/-private/store-service.ts +203 -108
- package/addon/-private/utils/is-non-empty-string.ts +1 -1
- package/addon/-private/utils/promise-record.ts +2 -3
- package/addon/-private/utils/uuid-polyfill.ts +58 -56
- package/package.json +5 -5
- package/addon/-private/backburner.js +0 -25
- package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -128
- package/addon/-private/record-arrays/record-array.ts +0 -328
- package/addon/-private/utils/weak-cache.ts +0 -125
|
@@ -1,28 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@module @ember-data/store
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import { A } from '@ember/array';
|
|
6
|
-
import { assert } from '@ember/debug';
|
|
7
|
-
import { _backburner as emberBackburner } from '@ember/runloop';
|
|
8
|
-
import { DEBUG } from '@glimmer/env';
|
|
9
|
-
|
|
10
|
-
import type { CollectionResourceDocument, Meta } from '@ember-data/types/q/ember-data-json-api';
|
|
4
|
+
import type { CollectionResourceDocument } from '@ember-data/types/q/ember-data-json-api';
|
|
11
5
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
12
6
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
13
7
|
|
|
14
|
-
import
|
|
15
|
-
|
|
8
|
+
import IdentifierArray, {
|
|
9
|
+
Collection,
|
|
10
|
+
CollectionCreateOptions,
|
|
11
|
+
IDENTIFIER_ARRAY_TAG,
|
|
12
|
+
SOURCE,
|
|
13
|
+
} from '../record-arrays/identifier-array';
|
|
16
14
|
import type Store from '../store-service';
|
|
17
|
-
import WeakCache from '../utils/weak-cache';
|
|
18
15
|
|
|
19
|
-
const RecordArraysCache = new
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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));
|
|
23
68
|
}
|
|
24
69
|
|
|
25
|
-
|
|
70
|
+
type ChangeSet = Map<StableRecordIdentifier, 'add' | 'del'>;
|
|
26
71
|
|
|
27
72
|
/**
|
|
28
73
|
@class RecordArrayManager
|
|
@@ -32,390 +77,301 @@ class RecordArrayManager {
|
|
|
32
77
|
declare store: Store;
|
|
33
78
|
declare isDestroying: boolean;
|
|
34
79
|
declare isDestroyed: boolean;
|
|
35
|
-
declare
|
|
36
|
-
declare
|
|
37
|
-
declare
|
|
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>;
|
|
38
85
|
|
|
39
86
|
constructor(options: { store: Store }) {
|
|
40
87
|
this.store = options.store;
|
|
41
88
|
this.isDestroying = false;
|
|
42
89
|
this.isDestroyed = false;
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
90
|
+
this._live = new Map();
|
|
91
|
+
this._managed = new Set();
|
|
92
|
+
this._pending = new Map();
|
|
93
|
+
this._staged = new Map();
|
|
94
|
+
this._identifiers = RecordArraysCache;
|
|
46
95
|
}
|
|
47
96
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* @internal
|
|
51
|
-
* @param {StableIdentifier} param
|
|
52
|
-
* @return {RecordArray} array
|
|
53
|
-
*/
|
|
54
|
-
getRecordArraysForIdentifier(identifier: StableRecordIdentifier): Set<RecordArray> {
|
|
55
|
-
return recordArraysForIdentifier(identifier);
|
|
56
|
-
}
|
|
97
|
+
_syncArray(array: IdentifierArray) {
|
|
98
|
+
const pending = this._pending.get(array);
|
|
57
99
|
|
|
58
|
-
|
|
59
|
-
if (this.isDestroying || this.isDestroyed) {
|
|
100
|
+
if (!pending || this.isDestroying || this.isDestroyed) {
|
|
60
101
|
return;
|
|
61
102
|
}
|
|
62
|
-
let identifiersToRemove: StableRecordIdentifier[] = [];
|
|
63
|
-
let cache = this.store._instanceCache;
|
|
64
|
-
|
|
65
|
-
for (let j = 0; j < identifiers.length; j++) {
|
|
66
|
-
let i = identifiers[j];
|
|
67
|
-
// mark identifiers, so they can once again be processed by the
|
|
68
|
-
// recordArrayManager
|
|
69
|
-
pendingForIdentifier.delete(i);
|
|
70
|
-
// build up a set of models to ensure we have purged correctly;
|
|
71
|
-
if (!cache.recordIsLoaded(i, true)) {
|
|
72
|
-
identifiersToRemove.push(i);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let array = this._liveRecordArrays[modelName];
|
|
77
|
-
if (array) {
|
|
78
|
-
// TODO: skip if it only changed
|
|
79
|
-
// process liveRecordArrays
|
|
80
|
-
updateLiveRecordArray(this.store, array, identifiers);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// process adapterPopulatedRecordArrays
|
|
84
|
-
if (identifiersToRemove.length > 0) {
|
|
85
|
-
removeFromAdapterPopulatedRecordArrays(this.store, identifiersToRemove);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
103
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this._pendingIdentifiers = Object.create(null) as Dict<StableRecordIdentifier[]>;
|
|
92
|
-
|
|
93
|
-
for (let modelName in pending) {
|
|
94
|
-
this._flushPendingIdentifiersForModelName(modelName, pending[modelName]!);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
_syncLiveRecordArray(array: RecordArray, modelName: string) {
|
|
99
|
-
assert(
|
|
100
|
-
`recordArrayManger.syncLiveRecordArray expects modelName not modelClass as the second param`,
|
|
101
|
-
typeof modelName === 'string'
|
|
102
|
-
);
|
|
103
|
-
let pending = this._pendingIdentifiers[modelName];
|
|
104
|
-
|
|
105
|
-
if (!Array.isArray(pending)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
let hasNoPotentialDeletions = pending.length === 0;
|
|
109
|
-
let listSize = this.store._instanceCache.peekList[modelName]?.size;
|
|
110
|
-
let hasNoInsertionsOrRemovals = listSize === array.length;
|
|
111
|
-
|
|
112
|
-
/*
|
|
113
|
-
Ideally the recordArrayManager has knowledge of the changes to be applied to
|
|
114
|
-
liveRecordArrays, and is capable of strategically flushing those changes and applying
|
|
115
|
-
small diffs if desired. However, until we've refactored recordArrayManager, this dirty
|
|
116
|
-
check prevents us from unnecessarily wiping out live record arrays returned by peekAll.
|
|
117
|
-
*/
|
|
118
|
-
if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this._flushPendingIdentifiersForModelName(modelName, pending);
|
|
123
|
-
// TODO make this a map
|
|
124
|
-
// TODO we probably do way too much work here, probably can just take
|
|
125
|
-
// current all-known state and filter visible.
|
|
126
|
-
// We can then provide this directly to the RecordArray instance.
|
|
127
|
-
// If we deprecate RecordArray being an ArrayProxy this will be stellar.
|
|
128
|
-
delete this._pendingIdentifiers[modelName];
|
|
129
|
-
|
|
130
|
-
let identifiers = this._visibleIdentifiersByType(modelName);
|
|
131
|
-
let identifiersToAdd: StableRecordIdentifier[] = [];
|
|
132
|
-
for (let i = 0; i < identifiers.length; i++) {
|
|
133
|
-
let identifier = identifiers[i];
|
|
134
|
-
let recordArrays = recordArraysForIdentifier(identifier);
|
|
135
|
-
if (recordArrays.has(array) === false) {
|
|
136
|
-
recordArrays.add(array);
|
|
137
|
-
identifiersToAdd.push(identifier);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (identifiersToAdd.length) {
|
|
142
|
-
array._pushIdentifiers(identifiersToAdd);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
_didUpdateAll(modelName: string): void {
|
|
147
|
-
let recordArray = this._liveRecordArrays[modelName];
|
|
148
|
-
if (recordArray) {
|
|
149
|
-
recordArray.isUpdating = false;
|
|
150
|
-
// TODO potentially we should sync here, currently
|
|
151
|
-
// this occurs as a side-effect of individual records updating
|
|
152
|
-
// this._syncLiveRecordArray(recordArray, modelName);
|
|
153
|
-
}
|
|
104
|
+
sync(array, pending);
|
|
105
|
+
this._pending.delete(array);
|
|
154
106
|
}
|
|
155
107
|
|
|
156
108
|
/**
|
|
157
109
|
Get the `RecordArray` for a modelName, which contains all loaded records of
|
|
158
110
|
given modelName.
|
|
159
111
|
|
|
160
|
-
@method
|
|
112
|
+
@method liveArrayFor
|
|
161
113
|
@internal
|
|
162
114
|
@param {String} modelName
|
|
163
115
|
@return {RecordArray}
|
|
164
116
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
}
|
|
172
129
|
|
|
173
|
-
if (array) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.
|
|
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);
|
|
182
139
|
}
|
|
183
140
|
|
|
184
141
|
return array;
|
|
185
142
|
}
|
|
186
143
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
Create a `RecordArray` for a modelName.
|
|
204
|
-
|
|
205
|
-
@method createRecordArray
|
|
206
|
-
@internal
|
|
207
|
-
@param {String} modelName
|
|
208
|
-
@param {Array} [identifiers]
|
|
209
|
-
@return {RecordArray}
|
|
210
|
-
*/
|
|
211
|
-
createRecordArray(modelName: string, identifiers: StableRecordIdentifier[] = []): RecordArray {
|
|
212
|
-
assert(
|
|
213
|
-
`recordArrayManger.createRecordArray expects modelName not modelClass as the param`,
|
|
214
|
-
typeof modelName === 'string'
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
let array = RecordArray.create({
|
|
218
|
-
modelName,
|
|
219
|
-
content: A(identifiers || []),
|
|
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,
|
|
220
158
|
store: this.store,
|
|
221
|
-
isLoaded: true,
|
|
222
159
|
manager: this,
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
160
|
+
};
|
|
161
|
+
let array = new Collection(options);
|
|
162
|
+
this._managed.add(array);
|
|
163
|
+
if (config.identifiers) {
|
|
164
|
+
associate(array, config.identifiers);
|
|
227
165
|
}
|
|
228
166
|
|
|
229
167
|
return array;
|
|
230
168
|
}
|
|
231
169
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
*/
|
|
241
|
-
createAdapterPopulatedRecordArray(
|
|
242
|
-
modelName: string,
|
|
243
|
-
query: Dict<unknown> | undefined,
|
|
244
|
-
identifiers: StableRecordIdentifier[],
|
|
245
|
-
payload?: CollectionResourceDocument
|
|
246
|
-
): AdapterPopulatedRecordArray {
|
|
247
|
-
assert(
|
|
248
|
-
`recordArrayManger.createAdapterPopulatedRecordArray expects modelName not modelClass as the first param, received ${modelName}`,
|
|
249
|
-
typeof modelName === 'string'
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
let array: AdapterPopulatedRecordArray;
|
|
253
|
-
if (Array.isArray(identifiers)) {
|
|
254
|
-
array = AdapterPopulatedRecordArray.create({
|
|
255
|
-
modelName,
|
|
256
|
-
query: query,
|
|
257
|
-
content: A(identifiers),
|
|
258
|
-
store: this.store,
|
|
259
|
-
manager: this,
|
|
260
|
-
isLoaded: true,
|
|
261
|
-
isUpdating: false,
|
|
262
|
-
// TODO this assign kills the root reference but a deep-copy would be required
|
|
263
|
-
// for both meta and links to actually not be by-ref. We whould likely change
|
|
264
|
-
// this to a dev-only deep-freeze.
|
|
265
|
-
meta: Object.assign({} as Meta, payload?.meta),
|
|
266
|
-
links: Object.assign({}, payload?.links),
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
this._associateWithRecordArray(identifiers, array);
|
|
270
|
-
} else {
|
|
271
|
-
array = AdapterPopulatedRecordArray.create({
|
|
272
|
-
modelName,
|
|
273
|
-
query: query,
|
|
274
|
-
content: A<StableRecordIdentifier>(),
|
|
275
|
-
isLoaded: false,
|
|
276
|
-
store: this.store,
|
|
277
|
-
manager: this,
|
|
278
|
-
});
|
|
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;
|
|
279
178
|
}
|
|
179
|
+
}
|
|
280
180
|
|
|
281
|
-
|
|
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
|
+
}
|
|
282
189
|
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
}
|
|
285
207
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
+
}
|
|
289
213
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (array === liveRecordArrayForType) {
|
|
305
|
-
delete this._liveRecordArrays[modelName];
|
|
306
|
-
}
|
|
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);
|
|
307
228
|
}
|
|
229
|
+
pending.set(liveArray, changes);
|
|
308
230
|
}
|
|
231
|
+
|
|
232
|
+
return pending;
|
|
309
233
|
}
|
|
310
234
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
248
|
}
|
|
324
249
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
});
|
|
332
265
|
}
|
|
333
|
-
|
|
266
|
+
}
|
|
334
267
|
|
|
335
|
-
|
|
336
|
-
|
|
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
|
+
});
|
|
337
283
|
}
|
|
284
|
+
}
|
|
338
285
|
|
|
339
|
-
|
|
286
|
+
identifierChanged(identifier: StableRecordIdentifier): void {
|
|
287
|
+
let newState = this.store._instanceCache.recordIsLoaded(identifier, true);
|
|
340
288
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
289
|
+
if (newState) {
|
|
290
|
+
this.identifierAdded(identifier);
|
|
291
|
+
} else {
|
|
292
|
+
this.identifierRemoved(identifier);
|
|
345
293
|
}
|
|
346
|
-
|
|
347
|
-
// TODO do we still need this schedule?
|
|
348
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
349
|
-
emberBackburner.schedule('actions', this, this._flush);
|
|
350
294
|
}
|
|
351
295
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
this.
|
|
296
|
+
clear() {
|
|
297
|
+
this._live.forEach((array) => array.destroy());
|
|
298
|
+
this._managed.forEach((array) => array.destroy());
|
|
299
|
+
this._managed.clear();
|
|
300
|
+
RecordArraysCache.clear();
|
|
356
301
|
}
|
|
357
302
|
|
|
358
303
|
destroy() {
|
|
359
304
|
this.isDestroying = true;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function removeFromArray(array: RecordArray[], item: RecordArray): boolean {
|
|
367
|
-
let index = array.indexOf(item);
|
|
368
|
-
|
|
369
|
-
if (index !== -1) {
|
|
370
|
-
array.splice(index, 1);
|
|
371
|
-
return true;
|
|
305
|
+
this.clear();
|
|
306
|
+
this._live.clear();
|
|
307
|
+
this.isDestroyed = true;
|
|
372
308
|
}
|
|
373
|
-
|
|
374
|
-
return false;
|
|
375
309
|
}
|
|
376
310
|
|
|
377
|
-
function
|
|
378
|
-
let identifiersToAdd: StableRecordIdentifier[] = [];
|
|
379
|
-
let identifiersToRemove: StableRecordIdentifier[] = [];
|
|
380
|
-
const cache = store._instanceCache;
|
|
381
|
-
|
|
311
|
+
function associate(array: Collection, identifiers: StableRecordIdentifier[]) {
|
|
382
312
|
for (let i = 0; i < identifiers.length; i++) {
|
|
383
313
|
let identifier = identifiers[i];
|
|
384
|
-
let
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
identifiersToAdd.push(identifier);
|
|
389
|
-
recordArrays.add(recordArray);
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
identifiersToRemove.push(identifier);
|
|
393
|
-
recordArrays.delete(recordArray);
|
|
314
|
+
let cache = RecordArraysCache.get(identifier);
|
|
315
|
+
if (!cache) {
|
|
316
|
+
cache = new Set();
|
|
317
|
+
RecordArraysCache.set(identifier, cache);
|
|
394
318
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (identifiersToAdd.length > 0) {
|
|
398
|
-
recordArray._pushIdentifiers(identifiersToAdd);
|
|
399
|
-
}
|
|
400
|
-
if (identifiersToRemove.length > 0) {
|
|
401
|
-
recordArray._removeIdentifiers(identifiersToRemove);
|
|
319
|
+
cache.add(array);
|
|
402
320
|
}
|
|
403
321
|
}
|
|
404
322
|
|
|
405
|
-
function
|
|
323
|
+
function disassociate(array: Collection, identifiers: StableRecordIdentifier[]) {
|
|
406
324
|
for (let i = 0; i < identifiers.length; i++) {
|
|
407
|
-
|
|
325
|
+
disassociateIdentifier(array, identifiers[i]);
|
|
408
326
|
}
|
|
409
327
|
}
|
|
410
328
|
|
|
411
|
-
function
|
|
412
|
-
|
|
329
|
+
export function disassociateIdentifier(array: Collection, identifier: StableRecordIdentifier) {
|
|
330
|
+
let cache = RecordArraysCache.get(identifier);
|
|
331
|
+
if (cache) {
|
|
332
|
+
cache.delete(array);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
413
335
|
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
}
|
|
416
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
|
+
}
|
|
417
362
|
|
|
418
|
-
|
|
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
|
+
}
|
|
419
375
|
}
|
|
420
376
|
|
|
421
377
|
export default RecordArrayManager;
|