@ember-data/store 4.7.0-beta.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
package/addon/-debug/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
2
|
import { DEBUG } from '@glimmer/env';
|
|
3
3
|
|
|
4
|
+
import { DEPRECATE_NON_EXPLICIT_POLYMORPHISM } from '@ember-data/private-build-infra/deprecations';
|
|
5
|
+
|
|
4
6
|
/*
|
|
5
7
|
Assert that `addedRecord` has a valid type so it can be added to the
|
|
6
8
|
relationship of the `record`.
|
|
@@ -9,7 +11,7 @@ import { DEBUG } from '@glimmer/env';
|
|
|
9
11
|
relationship (specified via `relationshipMeta`) of the `record`.
|
|
10
12
|
|
|
11
13
|
This utility should only be used internally, as both record parameters must
|
|
12
|
-
be
|
|
14
|
+
be stable record identifiers and the `relationshipMeta` needs to be the meta
|
|
13
15
|
information about the relationship, retrieved via
|
|
14
16
|
`record.relationshipFor(key)`.
|
|
15
17
|
*/
|
|
@@ -29,18 +31,38 @@ if (DEBUG) {
|
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
assertPolymorphicType = function assertPolymorphicType(parentIdentifier, parentDefinition, addedIdentifier, store) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
let asserted = false;
|
|
35
|
+
|
|
36
|
+
if (parentDefinition.inverseIsImplicit) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (parentDefinition.isPolymorphic) {
|
|
40
|
+
let meta = store.getSchemaDefinitionService().relationshipsDefinitionFor(addedIdentifier)[
|
|
41
|
+
parentDefinition.inverseKey
|
|
42
|
+
];
|
|
43
|
+
if (meta?.options?.as) {
|
|
44
|
+
asserted = true;
|
|
45
|
+
assert(
|
|
46
|
+
`The schema for the relationship '${parentDefinition.inverseKey}' on '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. The definition should specify 'as: "${parentDefinition.type}"' in options.`,
|
|
47
|
+
meta.options.as === parentDefinition.type
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM && !asserted) {
|
|
53
|
+
store = store._store ? store._store : store; // allow usage with storeWrapper
|
|
54
|
+
let addedModelName = addedIdentifier.type;
|
|
55
|
+
let parentModelName = parentIdentifier.type;
|
|
56
|
+
let key = parentDefinition.key;
|
|
57
|
+
let relationshipModelName = parentDefinition.type;
|
|
58
|
+
let relationshipClass = store.modelFor(relationshipModelName);
|
|
59
|
+
let addedClass = store.modelFor(addedModelName);
|
|
60
|
+
|
|
61
|
+
let assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`;
|
|
62
|
+
let isPolymorphic = checkPolymorphic(relationshipClass, addedClass);
|
|
63
|
+
|
|
64
|
+
assert(assertionMessage, isPolymorphic);
|
|
65
|
+
}
|
|
44
66
|
};
|
|
45
67
|
}
|
|
46
68
|
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import { assert, warn } from '@ember/debug';
|
|
5
5
|
import { DEBUG } from '@glimmer/env';
|
|
6
6
|
|
|
7
|
+
import { getOwnConfig, macroCondition } from '@embroider/macros';
|
|
8
|
+
|
|
9
|
+
import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging';
|
|
7
10
|
import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
|
|
8
11
|
import type {
|
|
9
12
|
ForgetMethod,
|
|
@@ -18,13 +21,13 @@ import type {
|
|
|
18
21
|
} from '@ember-data/types/q/identifier';
|
|
19
22
|
import type { ConfidentDict } from '@ember-data/types/q/utils';
|
|
20
23
|
|
|
21
|
-
import coerceId from '
|
|
22
|
-
import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
24
|
+
import coerceId from '../utils/coerce-id';
|
|
25
|
+
import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '../utils/identifer-debug-consts';
|
|
26
|
+
import isNonEmptyString from '../utils/is-non-empty-string';
|
|
27
|
+
import normalizeModelName from '../utils/normalize-model-name';
|
|
28
|
+
import installPolyfill from '../utils/uuid-polyfill';
|
|
26
29
|
|
|
27
|
-
const IDENTIFIERS = new
|
|
30
|
+
const IDENTIFIERS = new Set();
|
|
28
31
|
|
|
29
32
|
export function isStableIdentifier(identifier: Object): identifier is StableRecordIdentifier {
|
|
30
33
|
return IDENTIFIERS.has(identifier);
|
|
@@ -33,6 +36,10 @@ export function isStableIdentifier(identifier: Object): identifier is StableReco
|
|
|
33
36
|
const isFastBoot = typeof FastBoot !== 'undefined';
|
|
34
37
|
const _crypto: Crypto = isFastBoot ? (FastBoot.require('crypto') as Crypto) : window.crypto;
|
|
35
38
|
|
|
39
|
+
if (macroCondition(getOwnConfig<{ polyfillUUID: boolean }>().polyfillUUID)) {
|
|
40
|
+
installPolyfill();
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
function uuidv4(): string {
|
|
37
44
|
return _crypto.randomUUID();
|
|
38
45
|
}
|
|
@@ -47,10 +54,9 @@ function freeze<T>(obj: T): T {
|
|
|
47
54
|
interface KeyOptions {
|
|
48
55
|
lid: IdentifierMap;
|
|
49
56
|
id: IdentifierMap;
|
|
50
|
-
_allIdentifiers: StableRecordIdentifier[];
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
type IdentifierMap =
|
|
59
|
+
type IdentifierMap = Map<string, StableRecordIdentifier>;
|
|
54
60
|
type TypeMap = ConfidentDict<KeyOptions>;
|
|
55
61
|
export type MergeMethod = (
|
|
56
62
|
targetIdentifier: StableRecordIdentifier,
|
|
@@ -79,12 +85,15 @@ export function setIdentifierResetMethod(method: ResetMethod | null): void {
|
|
|
79
85
|
configuredResetMethod = method;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
type WithLid = { lid: string };
|
|
89
|
+
type WithId = { id: string | null; type: string };
|
|
90
|
+
|
|
82
91
|
function defaultGenerationMethod(data: ResourceData | { type: string }, bucket: IdentifierBucket): string {
|
|
83
|
-
if (
|
|
84
|
-
return data.lid;
|
|
92
|
+
if (isNonEmptyString((data as WithLid).lid)) {
|
|
93
|
+
return (data as WithLid).lid;
|
|
85
94
|
}
|
|
86
|
-
if (
|
|
87
|
-
let { type, id } = data;
|
|
95
|
+
if ((data as WithId).id !== undefined) {
|
|
96
|
+
let { type, id } = data as WithId;
|
|
88
97
|
// TODO: add test for id not a string
|
|
89
98
|
if (isNonEmptyString(coerceId(id))) {
|
|
90
99
|
return `@lid:${normalizeModelName(type)}-${id}`;
|
|
@@ -97,7 +106,7 @@ function defaultEmptyCallback(...args: any[]): any {}
|
|
|
97
106
|
|
|
98
107
|
let DEBUG_MAP;
|
|
99
108
|
if (DEBUG) {
|
|
100
|
-
DEBUG_MAP = new
|
|
109
|
+
DEBUG_MAP = new WeakMap<StableRecordIdentifier, StableRecordIdentifier>();
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
/**
|
|
@@ -114,19 +123,16 @@ if (DEBUG) {
|
|
|
114
123
|
@public
|
|
115
124
|
*/
|
|
116
125
|
export class IdentifierCache {
|
|
117
|
-
// Typescript still leaks private properties in the final
|
|
118
|
-
// compiled class, so we may want to move these from _underscore
|
|
119
|
-
// to a WeakMap to avoid leaking
|
|
120
|
-
// currently we leak this for test purposes
|
|
121
126
|
_cache = {
|
|
122
|
-
lids:
|
|
127
|
+
lids: new Map<string, StableRecordIdentifier>(),
|
|
123
128
|
types: Object.create(null) as TypeMap,
|
|
124
129
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
declare _generate: GenerationMethod;
|
|
131
|
+
declare _update: UpdateMethod;
|
|
132
|
+
declare _forget: ForgetMethod;
|
|
133
|
+
declare _reset: ResetMethod;
|
|
134
|
+
declare _merge: MergeMethod;
|
|
135
|
+
declare _isDefaultConfig: boolean;
|
|
130
136
|
|
|
131
137
|
constructor() {
|
|
132
138
|
// we cache the user configuredGenerationMethod at init because it must
|
|
@@ -136,13 +142,14 @@ export class IdentifierCache {
|
|
|
136
142
|
this._forget = configuredForgetMethod || defaultEmptyCallback;
|
|
137
143
|
this._reset = configuredResetMethod || defaultEmptyCallback;
|
|
138
144
|
this._merge = defaultEmptyCallback;
|
|
145
|
+
this._isDefaultConfig = !configuredGenerationMethod;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
/**
|
|
142
149
|
* Internal hook to allow management of merge conflicts with identifiers.
|
|
143
150
|
*
|
|
144
|
-
* we allow late binding of this private internal merge so that
|
|
145
|
-
* can insert itself here to handle elimination of duplicates
|
|
151
|
+
* we allow late binding of this private internal merge so that
|
|
152
|
+
* the cache can insert itself here to handle elimination of duplicates
|
|
146
153
|
*
|
|
147
154
|
* @method __configureMerge
|
|
148
155
|
* @private
|
|
@@ -155,12 +162,9 @@ export class IdentifierCache {
|
|
|
155
162
|
* @method _getRecordIdentifier
|
|
156
163
|
* @private
|
|
157
164
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
shouldGenerate: false
|
|
162
|
-
): StableRecordIdentifier | undefined;
|
|
163
|
-
private _getRecordIdentifier(
|
|
165
|
+
_getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
|
|
166
|
+
_getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: false): StableRecordIdentifier | undefined;
|
|
167
|
+
_getRecordIdentifier(
|
|
164
168
|
resource: ResourceIdentifierObject,
|
|
165
169
|
shouldGenerate: boolean = false
|
|
166
170
|
): StableRecordIdentifier | undefined {
|
|
@@ -168,22 +172,35 @@ export class IdentifierCache {
|
|
|
168
172
|
if (isStableIdentifier(resource)) {
|
|
169
173
|
if (DEBUG) {
|
|
170
174
|
// TODO should we instead just treat this case as a new generation skipping the short circuit?
|
|
171
|
-
if (!
|
|
175
|
+
if (!this._cache.lids.has(resource.lid) || this._cache.lids.get(resource.lid) !== resource) {
|
|
172
176
|
throw new Error(`The supplied identifier ${resource} does not belong to this store instance`);
|
|
173
177
|
}
|
|
174
178
|
}
|
|
179
|
+
if (LOG_IDENTIFIERS) {
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
console.log(`Identifiers: Peeked Identifier was already Stable ${String(resource)}`);
|
|
182
|
+
}
|
|
175
183
|
return resource;
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
let lid = coerceId(resource.lid);
|
|
179
|
-
let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids
|
|
187
|
+
let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids.get(lid) : undefined;
|
|
180
188
|
|
|
181
189
|
if (identifier !== undefined) {
|
|
190
|
+
if (LOG_IDENTIFIERS) {
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.log(`Identifiers: cache HIT ${identifier}`, resource);
|
|
193
|
+
}
|
|
182
194
|
return identifier;
|
|
183
195
|
}
|
|
184
196
|
|
|
197
|
+
if (LOG_IDENTIFIERS) {
|
|
198
|
+
// eslint-disable-next-line no-console
|
|
199
|
+
console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
if (shouldGenerate === false) {
|
|
186
|
-
if (!(
|
|
203
|
+
if (!(resource as ExistingResourceObject).type || !(resource as ExistingResourceObject).id) {
|
|
187
204
|
return;
|
|
188
205
|
}
|
|
189
206
|
}
|
|
@@ -198,31 +215,35 @@ export class IdentifierCache {
|
|
|
198
215
|
|
|
199
216
|
// go straight for the stable RecordIdentifier key'd to `lid`
|
|
200
217
|
if (lid !== null) {
|
|
201
|
-
identifier = keyOptions.lid
|
|
218
|
+
identifier = keyOptions.lid.get(lid);
|
|
202
219
|
}
|
|
203
220
|
|
|
204
221
|
// we may have not seen this resource before
|
|
205
222
|
// but just in case we check our own secondary lookup (`id`)
|
|
206
223
|
if (identifier === undefined && id !== null) {
|
|
207
|
-
identifier = keyOptions.id
|
|
224
|
+
identifier = keyOptions.id.get(id);
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
if (identifier === undefined) {
|
|
211
228
|
// we have definitely not seen this resource before
|
|
212
229
|
// so we allow the user configured `GenerationMethod` to tell us
|
|
213
230
|
let newLid = this._generate(resource, 'record');
|
|
231
|
+
if (LOG_IDENTIFIERS) {
|
|
232
|
+
// eslint-disable-next-line no-console
|
|
233
|
+
console.log(`Identifiers: lid ${newLid} determined for resource`, resource);
|
|
234
|
+
}
|
|
214
235
|
|
|
215
236
|
// we do this _even_ when `lid` is present because secondary lookups
|
|
216
237
|
// may need to be populated, but we enforce not giving us something
|
|
217
238
|
// different than expected
|
|
218
239
|
if (lid !== null && newLid !== lid) {
|
|
219
240
|
throw new Error(`You should not change the <lid> of a RecordIdentifier`);
|
|
220
|
-
} else if (lid === null) {
|
|
241
|
+
} else if (lid === null && !this._isDefaultConfig) {
|
|
221
242
|
// allow configuration to tell us that we have
|
|
222
243
|
// seen this `lid` before. E.g. a secondary lookup
|
|
223
244
|
// connects this resource to a previously seen
|
|
224
245
|
// resource.
|
|
225
|
-
identifier = keyOptions.lid
|
|
246
|
+
identifier = keyOptions.lid.get(newLid);
|
|
226
247
|
}
|
|
227
248
|
|
|
228
249
|
if (shouldGenerate === true) {
|
|
@@ -234,19 +255,27 @@ export class IdentifierCache {
|
|
|
234
255
|
if (DEBUG) {
|
|
235
256
|
// realistically if you hit this it means you changed `type` :/
|
|
236
257
|
// TODO consider how to handle type change assertions more gracefully
|
|
237
|
-
if (
|
|
258
|
+
if (this._cache.lids.has(identifier.lid)) {
|
|
238
259
|
throw new Error(`You should not change the <type> of a RecordIdentifier`);
|
|
239
260
|
}
|
|
240
261
|
}
|
|
241
|
-
this._cache.lids
|
|
262
|
+
this._cache.lids.set(identifier.lid, identifier);
|
|
242
263
|
|
|
243
264
|
// populate our primary lookup table
|
|
244
265
|
// TODO consider having the `lid` cache be
|
|
245
266
|
// one level up
|
|
246
|
-
keyOptions.lid
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
267
|
+
keyOptions.lid.set(identifier.lid, identifier);
|
|
268
|
+
|
|
269
|
+
if (LOG_IDENTIFIERS && shouldGenerate) {
|
|
270
|
+
// eslint-disable-next-line no-console
|
|
271
|
+
console.log(`Identifiers: generated ${String(identifier)} for`, resource);
|
|
272
|
+
if (resource[DEBUG_IDENTIFIER_BUCKET]) {
|
|
273
|
+
// eslint-disable-next-line no-console
|
|
274
|
+
console.trace(
|
|
275
|
+
`[WARNING] Identifiers: generated a new identifier from a previously used identifier. This is likely a bug.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
250
279
|
}
|
|
251
280
|
|
|
252
281
|
// populate our own secondary lookup table
|
|
@@ -257,7 +286,7 @@ export class IdentifierCache {
|
|
|
257
286
|
// because they may not match and we prefer
|
|
258
287
|
// what we've set via resource data
|
|
259
288
|
if (identifier.id !== null) {
|
|
260
|
-
keyOptions.id
|
|
289
|
+
keyOptions.id.set(identifier.id, identifier);
|
|
261
290
|
|
|
262
291
|
// TODO allow filling out of `id` here
|
|
263
292
|
// for the `username` non-client created
|
|
@@ -266,6 +295,15 @@ export class IdentifierCache {
|
|
|
266
295
|
}
|
|
267
296
|
}
|
|
268
297
|
|
|
298
|
+
if (LOG_IDENTIFIERS) {
|
|
299
|
+
if (!identifier && !shouldGenerate) {
|
|
300
|
+
// eslint-disable-next-line no-console
|
|
301
|
+
console.log(`Identifiers: cache MISS`, resource);
|
|
302
|
+
}
|
|
303
|
+
// eslint-disable-next-line no-console
|
|
304
|
+
console.groupEnd();
|
|
305
|
+
}
|
|
306
|
+
|
|
269
307
|
return identifier;
|
|
270
308
|
}
|
|
271
309
|
|
|
@@ -322,17 +360,22 @@ export class IdentifierCache {
|
|
|
322
360
|
|
|
323
361
|
// populate our unique table
|
|
324
362
|
if (DEBUG) {
|
|
325
|
-
if (
|
|
363
|
+
if (this._cache.lids.has(identifier.lid)) {
|
|
326
364
|
throw new Error(`The lid generated for the new record is not unique as it matches an existing identifier`);
|
|
327
365
|
}
|
|
328
366
|
}
|
|
329
|
-
this._cache.lids
|
|
367
|
+
this._cache.lids.set(identifier.lid, identifier);
|
|
330
368
|
|
|
331
369
|
// populate the type+lid cache
|
|
332
|
-
keyOptions.lid
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
370
|
+
keyOptions.lid.set(newLid, identifier);
|
|
371
|
+
if (data.id) {
|
|
372
|
+
keyOptions.id.set(data.id, identifier);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (LOG_IDENTIFIERS) {
|
|
376
|
+
// eslint-disable-next-line no-console
|
|
377
|
+
console.log(`Identifiers: createded identifier ${String(identifier)} for newly generated resource`, data);
|
|
378
|
+
}
|
|
336
379
|
|
|
337
380
|
return identifier;
|
|
338
381
|
}
|
|
@@ -362,13 +405,17 @@ export class IdentifierCache {
|
|
|
362
405
|
updateRecordIdentifier(identifierObject: RecordIdentifier, data: ResourceData): StableRecordIdentifier {
|
|
363
406
|
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
|
|
364
407
|
|
|
365
|
-
let newId =
|
|
408
|
+
let newId =
|
|
409
|
+
(data as ExistingResourceObject).id !== undefined ? coerceId((data as ExistingResourceObject).id) : null;
|
|
366
410
|
let existingIdentifier = detectMerge(this._cache.types, identifier, data, newId, this._cache.lids);
|
|
367
411
|
|
|
368
412
|
if (!existingIdentifier) {
|
|
369
413
|
// If the incoming type does not match the identifier type, we need to create an identifier for the incoming
|
|
370
414
|
// data so we can merge the incoming data with the existing identifier, see #7325 and #7363
|
|
371
|
-
if (
|
|
415
|
+
if (
|
|
416
|
+
(data as ExistingResourceObject).type &&
|
|
417
|
+
identifier.type !== normalizeModelName((data as ExistingResourceObject).type)
|
|
418
|
+
) {
|
|
372
419
|
let incomingDataResource = { ...data };
|
|
373
420
|
// Need to strip the lid from the incomingData in order force a new identifier creation
|
|
374
421
|
delete incomingDataResource.lid;
|
|
@@ -378,7 +425,21 @@ export class IdentifierCache {
|
|
|
378
425
|
|
|
379
426
|
if (existingIdentifier) {
|
|
380
427
|
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
381
|
-
|
|
428
|
+
let generatedIdentifier = identifier;
|
|
429
|
+
identifier = this._mergeRecordIdentifiers(
|
|
430
|
+
keyOptions,
|
|
431
|
+
generatedIdentifier,
|
|
432
|
+
existingIdentifier,
|
|
433
|
+
data,
|
|
434
|
+
newId as string
|
|
435
|
+
);
|
|
436
|
+
if (LOG_IDENTIFIERS) {
|
|
437
|
+
// eslint-disable-next-line no-console
|
|
438
|
+
console.log(
|
|
439
|
+
`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`,
|
|
440
|
+
data
|
|
441
|
+
);
|
|
442
|
+
}
|
|
382
443
|
}
|
|
383
444
|
|
|
384
445
|
let id = identifier.id;
|
|
@@ -387,12 +448,22 @@ export class IdentifierCache {
|
|
|
387
448
|
|
|
388
449
|
// add to our own secondary lookup table
|
|
389
450
|
if (id !== newId && newId !== null) {
|
|
451
|
+
if (LOG_IDENTIFIERS) {
|
|
452
|
+
// eslint-disable-next-line no-console
|
|
453
|
+
console.log(
|
|
454
|
+
`Identifiers: updated id for identifier ${identifier.lid} from '${id}' to '${newId}' for resource`,
|
|
455
|
+
data
|
|
456
|
+
);
|
|
457
|
+
}
|
|
390
458
|
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
391
|
-
keyOptions.id
|
|
459
|
+
keyOptions.id.set(newId, identifier);
|
|
392
460
|
|
|
393
461
|
if (id !== null) {
|
|
394
|
-
|
|
462
|
+
keyOptions.id.delete(id);
|
|
395
463
|
}
|
|
464
|
+
} else if (LOG_IDENTIFIERS) {
|
|
465
|
+
// eslint-disable-next-line no-console
|
|
466
|
+
console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data);
|
|
396
467
|
}
|
|
397
468
|
|
|
398
469
|
return identifier;
|
|
@@ -417,10 +488,10 @@ export class IdentifierCache {
|
|
|
417
488
|
this.forgetRecordIdentifier(abandoned);
|
|
418
489
|
|
|
419
490
|
// ensure a secondary cache entry for this id for the identifier we do keep
|
|
420
|
-
keyOptions.id
|
|
491
|
+
keyOptions.id.set(newId, kept);
|
|
421
492
|
// ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
|
|
422
493
|
let baseKeyOptions = getTypeIndex(this._cache.types, existingIdentifier.type);
|
|
423
|
-
baseKeyOptions.id
|
|
494
|
+
baseKeyOptions.id.set(newId, kept);
|
|
424
495
|
|
|
425
496
|
// make sure that the `lid` on the data we are processing matches the lid we kept
|
|
426
497
|
data.lid = kept.lid;
|
|
@@ -444,16 +515,17 @@ export class IdentifierCache {
|
|
|
444
515
|
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
|
|
445
516
|
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
446
517
|
if (identifier.id !== null) {
|
|
447
|
-
|
|
518
|
+
keyOptions.id.delete(identifier.id);
|
|
448
519
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
let index = keyOptions._allIdentifiers.indexOf(identifier);
|
|
453
|
-
keyOptions._allIdentifiers.splice(index, 1);
|
|
520
|
+
this._cache.lids.delete(identifier.lid);
|
|
521
|
+
keyOptions.lid.delete(identifier.lid);
|
|
454
522
|
|
|
455
523
|
IDENTIFIERS.delete(identifierObject);
|
|
456
524
|
this._forget(identifier, 'record');
|
|
525
|
+
if (LOG_IDENTIFIERS) {
|
|
526
|
+
// eslint-disable-next-line no-console
|
|
527
|
+
console.log(`Identifiers: released identifier ${identifierObject.lid}`);
|
|
528
|
+
}
|
|
457
529
|
}
|
|
458
530
|
|
|
459
531
|
destroy() {
|
|
@@ -466,9 +538,8 @@ function getTypeIndex(typeMap: TypeMap, type: string): KeyOptions {
|
|
|
466
538
|
|
|
467
539
|
if (typeIndex === undefined) {
|
|
468
540
|
typeIndex = {
|
|
469
|
-
lid:
|
|
470
|
-
id:
|
|
471
|
-
_allIdentifiers: [],
|
|
541
|
+
lid: new Map(),
|
|
542
|
+
id: new Map(),
|
|
472
543
|
};
|
|
473
544
|
typeMap[type] = typeIndex;
|
|
474
545
|
}
|
|
@@ -507,6 +578,10 @@ function makeStableRecordIdentifier(
|
|
|
507
578
|
let { type, id, lid } = recordIdentifier;
|
|
508
579
|
return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${type}:${id} (${lid})`;
|
|
509
580
|
},
|
|
581
|
+
toJSON() {
|
|
582
|
+
let { type, id, lid } = recordIdentifier;
|
|
583
|
+
return { type, id, lid };
|
|
584
|
+
},
|
|
510
585
|
};
|
|
511
586
|
wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated;
|
|
512
587
|
wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket;
|
|
@@ -568,8 +643,8 @@ function performRecordIdentifierUpdate(identifier: StableRecordIdentifier, data:
|
|
|
568
643
|
// for the multiple-cache-key scenario we "could"
|
|
569
644
|
// use a heuristic to guess the best id for display
|
|
570
645
|
// (usually when `data.id` is available and `data.attributes` is not)
|
|
571
|
-
if (
|
|
572
|
-
identifier.id = coerceId(data.id);
|
|
646
|
+
if ((data as ExistingResourceObject).id !== undefined) {
|
|
647
|
+
identifier.id = coerceId((data as ExistingResourceObject).id);
|
|
573
648
|
}
|
|
574
649
|
}
|
|
575
650
|
|
|
@@ -583,20 +658,20 @@ function detectMerge(
|
|
|
583
658
|
const { id, type, lid } = identifier;
|
|
584
659
|
if (id !== null && id !== newId && newId !== null) {
|
|
585
660
|
let keyOptions = getTypeIndex(typesCache, identifier.type);
|
|
586
|
-
let existingIdentifier = keyOptions.id
|
|
661
|
+
let existingIdentifier = keyOptions.id.get(newId);
|
|
587
662
|
|
|
588
663
|
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
589
664
|
} else {
|
|
590
|
-
let newType =
|
|
665
|
+
let newType = (data as ExistingResourceObject).type && normalizeModelName((data as ExistingResourceObject).type);
|
|
591
666
|
|
|
592
667
|
// If the ids and type are the same but lid is not the same, we should trigger a merge of the identifiers
|
|
593
668
|
if (id !== null && id === newId && newType === type && data.lid && data.lid !== lid) {
|
|
594
|
-
let existingIdentifier = lids
|
|
669
|
+
let existingIdentifier = lids.get(data.lid);
|
|
595
670
|
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
596
671
|
// If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
|
|
597
672
|
} else if (id !== null && id === newId && newType && newType !== type && data.lid && data.lid === lid) {
|
|
598
673
|
let keyOptions = getTypeIndex(typesCache, newType);
|
|
599
|
-
let existingIdentifier = keyOptions.id
|
|
674
|
+
let existingIdentifier = keyOptions.id.get(id);
|
|
600
675
|
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
601
676
|
}
|
|
602
677
|
}
|