@ember-data/store 4.8.0-alpha.5 → 4.8.0-alpha.6
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/caches/instance-cache.ts +8 -9
- package/addon/-private/index.ts +10 -6
- package/addon/-private/legacy-model-support/shim-model-class.ts +1 -0
- package/addon/-private/managers/record-array-manager.ts +138 -59
- package/addon/-private/managers/record-data-manager.ts +120 -128
- package/addon/-private/managers/record-data-store-wrapper.ts +7 -1
- package/addon/-private/managers/record-notification-manager.ts +1 -0
- package/addon/-private/network/fetch-manager.ts +16 -1
- package/addon/-private/network/snapshot-record-array.ts +6 -6
- 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 +76 -47
- package/addon/-private/utils/promise-record.ts +2 -3
- package/package.json +4 -4
- package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -99
- package/addon/-private/record-arrays/record-array.ts +0 -289
|
@@ -4,10 +4,9 @@ import { DEBUG } from '@glimmer/env';
|
|
|
4
4
|
import { importSync } from '@embroider/macros';
|
|
5
5
|
import { resolve } from 'rsvp';
|
|
6
6
|
|
|
7
|
-
import { V2CACHE_SINGLETON_MANAGER } from '@ember-data/canary-features';
|
|
8
7
|
import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
|
|
9
8
|
import { LOG_INSTANCE_CACHE } from '@ember-data/private-build-infra/debugging';
|
|
10
|
-
import { DEPRECATE_V1CACHE_STORE_APIS } from '@ember-data/private-build-infra/deprecations';
|
|
9
|
+
import { DEPRECATE_V1_RECORD_DATA, DEPRECATE_V1CACHE_STORE_APIS } from '@ember-data/private-build-infra/deprecations';
|
|
11
10
|
import type { Graph, peekGraph } from '@ember-data/record-data/-private/graph/index';
|
|
12
11
|
import type {
|
|
13
12
|
ExistingResourceIdentifierObject,
|
|
@@ -324,23 +323,23 @@ export class InstanceCache {
|
|
|
324
323
|
identifier.lid,
|
|
325
324
|
this._storeWrapper
|
|
326
325
|
);
|
|
327
|
-
if (
|
|
326
|
+
if (DEPRECATE_V1_RECORD_DATA) {
|
|
327
|
+
recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
|
|
328
|
+
} else {
|
|
328
329
|
recordData = this.__cacheManager =
|
|
329
330
|
this.__cacheManager || new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
|
|
330
|
-
} else {
|
|
331
|
-
recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
|
|
332
331
|
}
|
|
333
332
|
} else {
|
|
334
333
|
let recordDataInstance = this.store.createRecordDataFor(identifier, this._storeWrapper);
|
|
335
|
-
if (
|
|
334
|
+
if (DEPRECATE_V1_RECORD_DATA) {
|
|
335
|
+
recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
|
|
336
|
+
} else {
|
|
336
337
|
if (DEBUG) {
|
|
337
|
-
recordData = this.__cacheManager = this.__cacheManager || new SingletonRecordDataManager(
|
|
338
|
+
recordData = this.__cacheManager = this.__cacheManager || new SingletonRecordDataManager();
|
|
338
339
|
(recordData as SingletonRecordDataManager)._addRecordData(identifier, recordDataInstance as RecordData);
|
|
339
340
|
} else {
|
|
340
341
|
recordData = recordDataInstance as RecordData;
|
|
341
342
|
}
|
|
342
|
-
} else {
|
|
343
|
-
recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
|
|
344
343
|
}
|
|
345
344
|
}
|
|
346
345
|
setRecordDataFor(identifier, recordData);
|
package/addon/-private/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
setIdentifierUpdateMethod,
|
|
19
19
|
setIdentifierForgetMethod,
|
|
20
20
|
setIdentifierResetMethod,
|
|
21
|
+
isStableIdentifier,
|
|
21
22
|
} from './caches/identifier-cache';
|
|
22
23
|
|
|
23
24
|
export function normalizeModelName(modelName: string) {
|
|
@@ -41,12 +42,15 @@ export function normalizeModelName(modelName: string) {
|
|
|
41
42
|
// to also eliminate
|
|
42
43
|
export { default as coerceId } from './utils/coerce-id';
|
|
43
44
|
|
|
44
|
-
export {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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';
|
|
50
54
|
|
|
51
55
|
// // Used by tests
|
|
52
56
|
export { default as SnapshotRecordArray } from './network/snapshot-record-array';
|
|
@@ -15,6 +15,7 @@ export function getShimClass(store: Store, modelName: string): ShimModelClass {
|
|
|
15
15
|
shims = Object.create(null) as Dict<ShimModelClass>;
|
|
16
16
|
AvailableShims.set(store, shims);
|
|
17
17
|
}
|
|
18
|
+
|
|
18
19
|
let shim = shims[modelName];
|
|
19
20
|
if (shim === undefined) {
|
|
20
21
|
shim = shims[modelName] = new ShimModelClass(store, modelName);
|
|
@@ -1,23 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@module @ember-data/store
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import { A } from '@ember/array';
|
|
6
|
-
import { _backburner as emberBackburner } from '@ember/runloop';
|
|
7
|
-
|
|
8
4
|
import type { CollectionResourceDocument } from '@ember-data/types/q/ember-data-json-api';
|
|
9
5
|
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
10
6
|
import type { Dict } from '@ember-data/types/q/utils';
|
|
11
7
|
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
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
15
|
|
|
18
|
-
const RecordArraysCache = new Map<StableRecordIdentifier, Set<
|
|
16
|
+
const RecordArraysCache = new Map<StableRecordIdentifier, Set<Collection>>();
|
|
19
17
|
const FAKE_ARR = {};
|
|
20
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
|
+
|
|
21
70
|
type ChangeSet = Map<StableRecordIdentifier, 'add' | 'del'>;
|
|
22
71
|
|
|
23
72
|
/**
|
|
@@ -28,11 +77,10 @@ class RecordArrayManager {
|
|
|
28
77
|
declare store: Store;
|
|
29
78
|
declare isDestroying: boolean;
|
|
30
79
|
declare isDestroyed: boolean;
|
|
31
|
-
declare _live: Map<string,
|
|
32
|
-
declare _managed: Set<
|
|
33
|
-
declare _pending: Map<
|
|
34
|
-
declare
|
|
35
|
-
declare _identifiers: Map<StableRecordIdentifier, Set<AdapterPopulatedRecordArray>>;
|
|
80
|
+
declare _live: Map<string, IdentifierArray>;
|
|
81
|
+
declare _managed: Set<IdentifierArray>;
|
|
82
|
+
declare _pending: Map<IdentifierArray, ChangeSet>;
|
|
83
|
+
declare _identifiers: Map<StableRecordIdentifier, Set<Collection>>;
|
|
36
84
|
declare _staged: Map<string, ChangeSet>;
|
|
37
85
|
|
|
38
86
|
constructor(options: { store: Store }) {
|
|
@@ -43,18 +91,17 @@ class RecordArrayManager {
|
|
|
43
91
|
this._managed = new Set();
|
|
44
92
|
this._pending = new Map();
|
|
45
93
|
this._staged = new Map();
|
|
46
|
-
this._willFlush = false;
|
|
47
94
|
this._identifiers = RecordArraysCache;
|
|
48
95
|
}
|
|
49
96
|
|
|
50
|
-
_syncArray(array:
|
|
97
|
+
_syncArray(array: IdentifierArray) {
|
|
51
98
|
const pending = this._pending.get(array);
|
|
52
99
|
|
|
53
100
|
if (!pending || this.isDestroying || this.isDestroyed) {
|
|
54
101
|
return;
|
|
55
102
|
}
|
|
56
103
|
|
|
57
|
-
array
|
|
104
|
+
sync(array, pending);
|
|
58
105
|
this._pending.delete(array);
|
|
59
106
|
}
|
|
60
107
|
|
|
@@ -67,7 +114,7 @@ class RecordArrayManager {
|
|
|
67
114
|
@param {String} modelName
|
|
68
115
|
@return {RecordArray}
|
|
69
116
|
*/
|
|
70
|
-
liveArrayFor(type: string):
|
|
117
|
+
liveArrayFor(type: string): IdentifierArray {
|
|
71
118
|
let array = this._live.get(type);
|
|
72
119
|
let identifiers: StableRecordIdentifier[] = [];
|
|
73
120
|
let staged = this._staged.get(type);
|
|
@@ -81,19 +128,14 @@ class RecordArrayManager {
|
|
|
81
128
|
}
|
|
82
129
|
|
|
83
130
|
if (!array) {
|
|
84
|
-
array =
|
|
85
|
-
|
|
86
|
-
|
|
131
|
+
array = new IdentifierArray({
|
|
132
|
+
type,
|
|
133
|
+
identifiers,
|
|
87
134
|
store: this.store,
|
|
88
|
-
|
|
135
|
+
allowMutation: false,
|
|
89
136
|
manager: this,
|
|
90
137
|
});
|
|
91
138
|
this._live.set(type, array);
|
|
92
|
-
} else {
|
|
93
|
-
let pending = this._pending.get(array);
|
|
94
|
-
if (pending) {
|
|
95
|
-
array._notify();
|
|
96
|
-
}
|
|
97
139
|
}
|
|
98
140
|
|
|
99
141
|
return array;
|
|
@@ -104,18 +146,19 @@ class RecordArrayManager {
|
|
|
104
146
|
query?: Dict<unknown>;
|
|
105
147
|
identifiers?: StableRecordIdentifier[];
|
|
106
148
|
doc?: CollectionResourceDocument;
|
|
107
|
-
}):
|
|
108
|
-
let options:
|
|
109
|
-
|
|
149
|
+
}): Collection {
|
|
150
|
+
let options: CollectionCreateOptions = {
|
|
151
|
+
type: config.type,
|
|
110
152
|
links: config.doc?.links || null,
|
|
111
153
|
meta: config.doc?.meta || null,
|
|
112
154
|
query: config.query || null,
|
|
113
|
-
|
|
155
|
+
identifiers: config.identifiers || [],
|
|
114
156
|
isLoaded: !!config.identifiers?.length,
|
|
157
|
+
allowMutation: false,
|
|
115
158
|
store: this.store,
|
|
116
159
|
manager: this,
|
|
117
160
|
};
|
|
118
|
-
let array =
|
|
161
|
+
let array = new Collection(options);
|
|
119
162
|
this._managed.add(array);
|
|
120
163
|
if (config.identifiers) {
|
|
121
164
|
associate(array, config.identifiers);
|
|
@@ -124,35 +167,29 @@ class RecordArrayManager {
|
|
|
124
167
|
return array;
|
|
125
168
|
}
|
|
126
169
|
|
|
127
|
-
dirtyArray(array:
|
|
128
|
-
if (array === FAKE_ARR
|
|
170
|
+
dirtyArray(array: IdentifierArray): void {
|
|
171
|
+
if (array === FAKE_ARR) {
|
|
129
172
|
return;
|
|
130
173
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
_flush() {
|
|
137
|
-
this._willFlush = false;
|
|
138
|
-
let pending = this._pending;
|
|
139
|
-
pending.forEach((_changes, recordArray) => {
|
|
140
|
-
recordArray._notify();
|
|
141
|
-
});
|
|
174
|
+
let tag = array[IDENTIFIER_ARRAY_TAG];
|
|
175
|
+
if (!tag.shouldReset) {
|
|
176
|
+
tag.shouldReset = true;
|
|
177
|
+
tag.ref = null;
|
|
178
|
+
}
|
|
142
179
|
}
|
|
143
180
|
|
|
144
181
|
_getPendingFor(
|
|
145
182
|
identifier: StableRecordIdentifier,
|
|
146
183
|
includeManaged: boolean,
|
|
147
184
|
isRemove?: boolean
|
|
148
|
-
): Map<
|
|
185
|
+
): Map<IdentifierArray, ChangeSet> | void {
|
|
149
186
|
if (this.isDestroying || this.isDestroyed) {
|
|
150
187
|
return;
|
|
151
188
|
}
|
|
152
189
|
|
|
153
190
|
let liveArray = this._live.get(identifier.type);
|
|
154
191
|
const allPending = this._pending;
|
|
155
|
-
let pending: Map<
|
|
192
|
+
let pending: Map<IdentifierArray, ChangeSet> = new Map();
|
|
156
193
|
|
|
157
194
|
if (includeManaged) {
|
|
158
195
|
let managed = RecordArraysCache.get(identifier);
|
|
@@ -170,7 +207,7 @@ class RecordArrayManager {
|
|
|
170
207
|
|
|
171
208
|
// during unloadAll we can ignore removes since we've already
|
|
172
209
|
// cleared the array.
|
|
173
|
-
if (liveArray && liveArray.
|
|
210
|
+
if (liveArray && liveArray[SOURCE].length === 0 && isRemove) {
|
|
174
211
|
return pending;
|
|
175
212
|
}
|
|
176
213
|
|
|
@@ -182,7 +219,7 @@ class RecordArrayManager {
|
|
|
182
219
|
changes = new Map();
|
|
183
220
|
this._staged.set(identifier.type, changes);
|
|
184
221
|
}
|
|
185
|
-
pending.set(FAKE_ARR as
|
|
222
|
+
pending.set(FAKE_ARR as IdentifierArray, changes);
|
|
186
223
|
} else {
|
|
187
224
|
let changes = allPending.get(liveArray);
|
|
188
225
|
if (!changes) {
|
|
@@ -195,14 +232,13 @@ class RecordArrayManager {
|
|
|
195
232
|
return pending;
|
|
196
233
|
}
|
|
197
234
|
|
|
198
|
-
populateManagedArray(
|
|
199
|
-
array: AdapterPopulatedRecordArray,
|
|
200
|
-
identifiers: StableRecordIdentifier[],
|
|
201
|
-
payload: CollectionResourceDocument
|
|
202
|
-
) {
|
|
235
|
+
populateManagedArray(array: Collection, identifiers: StableRecordIdentifier[], payload: CollectionResourceDocument) {
|
|
203
236
|
this._pending.delete(array);
|
|
204
|
-
const
|
|
205
|
-
|
|
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;
|
|
206
242
|
array.meta = payload.meta || null;
|
|
207
243
|
array.links = payload.links || null;
|
|
208
244
|
array.isLoaded = true;
|
|
@@ -272,7 +308,7 @@ class RecordArrayManager {
|
|
|
272
308
|
}
|
|
273
309
|
}
|
|
274
310
|
|
|
275
|
-
function associate(array:
|
|
311
|
+
function associate(array: Collection, identifiers: StableRecordIdentifier[]) {
|
|
276
312
|
for (let i = 0; i < identifiers.length; i++) {
|
|
277
313
|
let identifier = identifiers[i];
|
|
278
314
|
let cache = RecordArraysCache.get(identifier);
|
|
@@ -283,16 +319,59 @@ function associate(array: AdapterPopulatedRecordArray, identifiers: StableRecord
|
|
|
283
319
|
cache.add(array);
|
|
284
320
|
}
|
|
285
321
|
}
|
|
286
|
-
|
|
322
|
+
|
|
323
|
+
function disassociate(array: Collection, identifiers: StableRecordIdentifier[]) {
|
|
287
324
|
for (let i = 0; i < identifiers.length; i++) {
|
|
288
325
|
disassociateIdentifier(array, identifiers[i]);
|
|
289
326
|
}
|
|
290
327
|
}
|
|
291
|
-
|
|
328
|
+
|
|
329
|
+
export function disassociateIdentifier(array: Collection, identifier: StableRecordIdentifier) {
|
|
292
330
|
let cache = RecordArraysCache.get(identifier);
|
|
293
331
|
if (cache) {
|
|
294
332
|
cache.delete(array);
|
|
295
333
|
}
|
|
296
334
|
}
|
|
297
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
|
+
|
|
298
377
|
export default RecordArrayManager;
|