@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.
- package/addon/-debug/index.js +35 -13
- package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
- package/addon/-private/caches/instance-cache.ts +690 -0
- package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
- package/addon/-private/index.ts +44 -24
- package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
- package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
- package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
- package/addon/-private/managers/record-array-manager.ts +377 -0
- package/addon/-private/managers/record-data-manager.ts +845 -0
- package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
- package/addon/-private/managers/record-notification-manager.ts +109 -0
- package/addon/-private/network/fetch-manager.ts +567 -0
- package/addon/-private/{finders.js → network/finders.js} +14 -17
- package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
- package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
- package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
- package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
- package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
- package/addon/-private/record-arrays/identifier-array.ts +924 -0
- package/addon/-private/{core-store.ts → store-service.ts} +574 -215
- package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
- package/addon/-private/{common.js → utils/common.js} +1 -2
- package/addon/-private/utils/construct-resource.ts +2 -2
- package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
- package/addon/-private/utils/is-non-empty-string.ts +1 -1
- package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
- package/addon/-private/utils/promise-record.ts +5 -6
- package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
- package/addon/-private/utils/uuid-polyfill.ts +73 -0
- package/package.json +12 -8
- package/addon/-private/backburner.js +0 -25
- package/addon/-private/errors-utils.js +0 -146
- package/addon/-private/fetch-manager.ts +0 -597
- package/addon/-private/identity-map.ts +0 -54
- package/addon/-private/instance-cache.ts +0 -387
- package/addon/-private/internal-model-factory.ts +0 -359
- package/addon/-private/internal-model-map.ts +0 -121
- package/addon/-private/model/internal-model.ts +0 -602
- package/addon/-private/record-array-manager.ts +0 -444
- package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
- package/addon/-private/record-arrays/record-array.ts +0 -318
- package/addon/-private/record-data-store-wrapper.ts +0 -243
- package/addon/-private/record-notification-manager.ts +0 -73
- 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
|
|
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
|
|
package/addon/-private/index.ts
CHANGED
|
@@ -2,38 +2,58 @@
|
|
|
2
2
|
@module @ember-data/store
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { assert, deprecate } from '@ember/debug';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { DEPRECATE_HELPERS } from '@ember-data/private-build-infra/deprecations';
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
14
|
-
import
|
|
15
|
-
import
|
|
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
|
-
|
|
32
|
-
|
|
32
|
+
___token!: Object;
|
|
33
|
+
___identifier: StableRecordIdentifier;
|
|
33
34
|
|
|
34
35
|
@tracked _ref = 0;
|
|
35
36
|
|
|
36
|
-
constructor(
|
|
37
|
-
this
|
|
38
|
-
this
|
|
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' || (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 '
|
|
14
|
-
import normalizeModelName from '
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 =
|
|
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 =
|
|
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 '../
|
|
8
|
-
|
|
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.
|
|
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
|
-
|
|
37
|
-
|
|
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;
|