@ember-data/store 4.10.0-alpha.4 → 4.10.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.js +1 -0
- package/addon/-private.js.map +1 -0
- package/addon/index-12e1fcb9.js +7660 -0
- package/addon/index-12e1fcb9.js.map +1 -0
- package/addon/index.js +1 -0
- package/addon/index.js.map +1 -0
- package/addon-main.js +90 -0
- package/package.json +44 -15
- package/addon/-private/caches/identifier-cache.ts +0 -686
- package/addon/-private/caches/instance-cache.ts +0 -694
- package/addon/-private/caches/record-data-for.ts +0 -34
- package/addon/-private/index.ts +0 -59
- package/addon/-private/legacy-model-support/record-reference.ts +0 -240
- package/addon/-private/legacy-model-support/schema-definition-service.ts +0 -148
- package/addon/-private/legacy-model-support/shim-model-class.ts +0 -92
- package/addon/-private/managers/record-array-manager.ts +0 -382
- package/addon/-private/managers/record-data-manager.ts +0 -845
- package/addon/-private/managers/record-data-store-wrapper.ts +0 -425
- package/addon/-private/managers/record-notification-manager.ts +0 -111
- package/addon/-private/network/fetch-manager.ts +0 -567
- package/addon/-private/network/finders.js +0 -104
- package/addon/-private/network/request-cache.ts +0 -132
- package/addon/-private/network/snapshot-record-array.ts +0 -209
- package/addon/-private/network/snapshot.ts +0 -563
- package/addon/-private/proxies/promise-proxies.ts +0 -228
- package/addon/-private/proxies/promise-proxy-base.js +0 -7
- package/addon/-private/record-arrays/identifier-array.ts +0 -974
- package/addon/-private/store-service.ts +0 -2904
- package/addon/-private/utils/coerce-id.ts +0 -41
- package/addon/-private/utils/common.js +0 -65
- package/addon/-private/utils/construct-resource.ts +0 -61
- package/addon/-private/utils/identifer-debug-consts.ts +0 -3
- package/addon/-private/utils/is-non-empty-string.ts +0 -3
- package/addon/-private/utils/normalize-model-name.ts +0 -21
- package/addon/-private/utils/promise-record.ts +0 -15
- package/addon/-private/utils/serializer-response.ts +0 -86
- package/addon/-private/utils/uuid-polyfill.ts +0 -73
- package/addon/index.ts +0 -14
- package/index.js +0 -49
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module @ember-data/store
|
|
3
|
-
*/
|
|
4
|
-
import { assert, warn } from '@ember/debug';
|
|
5
|
-
import { DEBUG } from '@glimmer/env';
|
|
6
|
-
|
|
7
|
-
import { getOwnConfig, macroCondition } from '@embroider/macros';
|
|
8
|
-
|
|
9
|
-
import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging';
|
|
10
|
-
import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
|
|
11
|
-
import type {
|
|
12
|
-
ForgetMethod,
|
|
13
|
-
GenerationMethod,
|
|
14
|
-
Identifier,
|
|
15
|
-
IdentifierBucket,
|
|
16
|
-
RecordIdentifier,
|
|
17
|
-
ResetMethod,
|
|
18
|
-
ResourceData,
|
|
19
|
-
StableRecordIdentifier,
|
|
20
|
-
UpdateMethod,
|
|
21
|
-
} from '@ember-data/types/q/identifier';
|
|
22
|
-
import type { ConfidentDict } from '@ember-data/types/q/utils';
|
|
23
|
-
|
|
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';
|
|
29
|
-
|
|
30
|
-
const IDENTIFIERS = new Set();
|
|
31
|
-
|
|
32
|
-
export function isStableIdentifier(identifier: Object): identifier is StableRecordIdentifier {
|
|
33
|
-
return IDENTIFIERS.has(identifier);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const isFastBoot = typeof FastBoot !== 'undefined';
|
|
37
|
-
const _crypto: Crypto = isFastBoot ? (FastBoot.require('crypto') as Crypto) : window.crypto;
|
|
38
|
-
|
|
39
|
-
if (macroCondition(getOwnConfig<{ polyfillUUID: boolean }>().polyfillUUID)) {
|
|
40
|
-
installPolyfill();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function uuidv4(): string {
|
|
44
|
-
assert(
|
|
45
|
-
'crypto.randomUUID needs to be avaliable. Some browsers incorrectly disallow it in insecure contexts. You maybe want to enable the polyfill: https://github.com/emberjs/data#randomuuid-polyfill',
|
|
46
|
-
_crypto.randomUUID
|
|
47
|
-
);
|
|
48
|
-
return _crypto.randomUUID();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function freeze<T>(obj: T): T {
|
|
52
|
-
if (typeof Object.freeze === 'function') {
|
|
53
|
-
return Object.freeze(obj);
|
|
54
|
-
}
|
|
55
|
-
return obj;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface KeyOptions {
|
|
59
|
-
lid: IdentifierMap;
|
|
60
|
-
id: IdentifierMap;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type IdentifierMap = Map<string, StableRecordIdentifier>;
|
|
64
|
-
type TypeMap = ConfidentDict<KeyOptions>;
|
|
65
|
-
export type MergeMethod = (
|
|
66
|
-
targetIdentifier: StableRecordIdentifier,
|
|
67
|
-
matchedIdentifier: StableRecordIdentifier,
|
|
68
|
-
resourceData: ResourceIdentifierObject | ExistingResourceObject
|
|
69
|
-
) => StableRecordIdentifier;
|
|
70
|
-
|
|
71
|
-
let configuredForgetMethod: ForgetMethod | null;
|
|
72
|
-
let configuredGenerationMethod: GenerationMethod | null;
|
|
73
|
-
let configuredResetMethod: ResetMethod | null;
|
|
74
|
-
let configuredUpdateMethod: UpdateMethod | null;
|
|
75
|
-
|
|
76
|
-
export function setIdentifierGenerationMethod(method: GenerationMethod | null): void {
|
|
77
|
-
configuredGenerationMethod = method;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function setIdentifierUpdateMethod(method: UpdateMethod | null): void {
|
|
81
|
-
configuredUpdateMethod = method;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function setIdentifierForgetMethod(method: ForgetMethod | null): void {
|
|
85
|
-
configuredForgetMethod = method;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function setIdentifierResetMethod(method: ResetMethod | null): void {
|
|
89
|
-
configuredResetMethod = method;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
type WithLid = { lid: string };
|
|
93
|
-
type WithId = { id: string | null; type: string };
|
|
94
|
-
|
|
95
|
-
function defaultGenerationMethod(data: ResourceData | { type: string }, bucket: IdentifierBucket): string {
|
|
96
|
-
if (isNonEmptyString((data as WithLid).lid)) {
|
|
97
|
-
return (data as WithLid).lid;
|
|
98
|
-
}
|
|
99
|
-
if ((data as WithId).id !== undefined) {
|
|
100
|
-
let { type, id } = data as WithId;
|
|
101
|
-
// TODO: add test for id not a string
|
|
102
|
-
if (isNonEmptyString(coerceId(id))) {
|
|
103
|
-
return `@lid:${normalizeModelName(type)}-${id}`;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return uuidv4();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function defaultEmptyCallback(...args: any[]): any {}
|
|
110
|
-
|
|
111
|
-
let DEBUG_MAP;
|
|
112
|
-
if (DEBUG) {
|
|
113
|
-
DEBUG_MAP = new WeakMap<StableRecordIdentifier, StableRecordIdentifier>();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Each instance of {Store} receives a unique instance of a IdentifierCache.
|
|
118
|
-
*
|
|
119
|
-
* This cache is responsible for assigning or retrieving the unique identify
|
|
120
|
-
* for arbitrary resource data encountered by the store. Data representing
|
|
121
|
-
* a unique resource or record should always be represented by the same
|
|
122
|
-
* identifier.
|
|
123
|
-
*
|
|
124
|
-
* It can be configured by consuming applications.
|
|
125
|
-
*
|
|
126
|
-
* @class IdentifierCache
|
|
127
|
-
@public
|
|
128
|
-
*/
|
|
129
|
-
export class IdentifierCache {
|
|
130
|
-
_cache = {
|
|
131
|
-
lids: new Map<string, StableRecordIdentifier>(),
|
|
132
|
-
types: Object.create(null) as TypeMap,
|
|
133
|
-
};
|
|
134
|
-
declare _generate: GenerationMethod;
|
|
135
|
-
declare _update: UpdateMethod;
|
|
136
|
-
declare _forget: ForgetMethod;
|
|
137
|
-
declare _reset: ResetMethod;
|
|
138
|
-
declare _merge: MergeMethod;
|
|
139
|
-
declare _isDefaultConfig: boolean;
|
|
140
|
-
|
|
141
|
-
constructor() {
|
|
142
|
-
// we cache the user configuredGenerationMethod at init because it must
|
|
143
|
-
// be configured prior and is not allowed to be changed
|
|
144
|
-
this._generate = configuredGenerationMethod || defaultGenerationMethod;
|
|
145
|
-
this._update = configuredUpdateMethod || defaultEmptyCallback;
|
|
146
|
-
this._forget = configuredForgetMethod || defaultEmptyCallback;
|
|
147
|
-
this._reset = configuredResetMethod || defaultEmptyCallback;
|
|
148
|
-
this._merge = defaultEmptyCallback;
|
|
149
|
-
this._isDefaultConfig = !configuredGenerationMethod;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Internal hook to allow management of merge conflicts with identifiers.
|
|
154
|
-
*
|
|
155
|
-
* we allow late binding of this private internal merge so that
|
|
156
|
-
* the cache can insert itself here to handle elimination of duplicates
|
|
157
|
-
*
|
|
158
|
-
* @method __configureMerge
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
|
-
__configureMerge(method: MergeMethod | null) {
|
|
162
|
-
this._merge = method || defaultEmptyCallback;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* @method _getRecordIdentifier
|
|
167
|
-
* @private
|
|
168
|
-
*/
|
|
169
|
-
_getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: true): StableRecordIdentifier;
|
|
170
|
-
_getRecordIdentifier(resource: ResourceIdentifierObject, shouldGenerate: false): StableRecordIdentifier | undefined;
|
|
171
|
-
_getRecordIdentifier(
|
|
172
|
-
resource: ResourceIdentifierObject,
|
|
173
|
-
shouldGenerate: boolean = false
|
|
174
|
-
): StableRecordIdentifier | undefined {
|
|
175
|
-
// short circuit if we're already the stable version
|
|
176
|
-
if (isStableIdentifier(resource)) {
|
|
177
|
-
if (DEBUG) {
|
|
178
|
-
// TODO should we instead just treat this case as a new generation skipping the short circuit?
|
|
179
|
-
if (!this._cache.lids.has(resource.lid) || this._cache.lids.get(resource.lid) !== resource) {
|
|
180
|
-
throw new Error(`The supplied identifier ${resource} does not belong to this store instance`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (LOG_IDENTIFIERS) {
|
|
184
|
-
// eslint-disable-next-line no-console
|
|
185
|
-
console.log(`Identifiers: Peeked Identifier was already Stable ${String(resource)}`);
|
|
186
|
-
}
|
|
187
|
-
return resource;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let lid = coerceId(resource.lid);
|
|
191
|
-
let identifier: StableRecordIdentifier | undefined = lid !== null ? this._cache.lids.get(lid) : undefined;
|
|
192
|
-
|
|
193
|
-
if (identifier !== undefined) {
|
|
194
|
-
if (LOG_IDENTIFIERS) {
|
|
195
|
-
// eslint-disable-next-line no-console
|
|
196
|
-
console.log(`Identifiers: cache HIT ${identifier}`, resource);
|
|
197
|
-
}
|
|
198
|
-
return identifier;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (LOG_IDENTIFIERS) {
|
|
202
|
-
// eslint-disable-next-line no-console
|
|
203
|
-
console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (shouldGenerate === false) {
|
|
207
|
-
if (!(resource as ExistingResourceObject).type || !(resource as ExistingResourceObject).id) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// `type` must always be present
|
|
213
|
-
assert('resource.type needs to be a string', 'type' in resource && isNonEmptyString(resource.type));
|
|
214
|
-
|
|
215
|
-
let type = resource.type && normalizeModelName(resource.type);
|
|
216
|
-
let id = coerceId(resource.id);
|
|
217
|
-
|
|
218
|
-
let keyOptions = getTypeIndex(this._cache.types, type);
|
|
219
|
-
|
|
220
|
-
// go straight for the stable RecordIdentifier key'd to `lid`
|
|
221
|
-
if (lid !== null) {
|
|
222
|
-
identifier = keyOptions.lid.get(lid);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// we may have not seen this resource before
|
|
226
|
-
// but just in case we check our own secondary lookup (`id`)
|
|
227
|
-
if (identifier === undefined && id !== null) {
|
|
228
|
-
identifier = keyOptions.id.get(id);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (identifier === undefined) {
|
|
232
|
-
// we have definitely not seen this resource before
|
|
233
|
-
// so we allow the user configured `GenerationMethod` to tell us
|
|
234
|
-
let newLid = this._generate(resource, 'record');
|
|
235
|
-
if (LOG_IDENTIFIERS) {
|
|
236
|
-
// eslint-disable-next-line no-console
|
|
237
|
-
console.log(`Identifiers: lid ${newLid} determined for resource`, resource);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// we do this _even_ when `lid` is present because secondary lookups
|
|
241
|
-
// may need to be populated, but we enforce not giving us something
|
|
242
|
-
// different than expected
|
|
243
|
-
if (lid !== null && newLid !== lid) {
|
|
244
|
-
throw new Error(`You should not change the <lid> of a RecordIdentifier`);
|
|
245
|
-
} else if (lid === null && !this._isDefaultConfig) {
|
|
246
|
-
// allow configuration to tell us that we have
|
|
247
|
-
// seen this `lid` before. E.g. a secondary lookup
|
|
248
|
-
// connects this resource to a previously seen
|
|
249
|
-
// resource.
|
|
250
|
-
identifier = keyOptions.lid.get(newLid);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (shouldGenerate === true) {
|
|
254
|
-
if (identifier === undefined) {
|
|
255
|
-
// if we still don't have an identifier, time to generate one
|
|
256
|
-
identifier = makeStableRecordIdentifier(id, type, newLid, 'record', false);
|
|
257
|
-
|
|
258
|
-
// populate our unique table
|
|
259
|
-
if (DEBUG) {
|
|
260
|
-
// realistically if you hit this it means you changed `type` :/
|
|
261
|
-
// TODO consider how to handle type change assertions more gracefully
|
|
262
|
-
if (this._cache.lids.has(identifier.lid)) {
|
|
263
|
-
throw new Error(`You should not change the <type> of a RecordIdentifier`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
this._cache.lids.set(identifier.lid, identifier);
|
|
267
|
-
|
|
268
|
-
// populate our primary lookup table
|
|
269
|
-
// TODO consider having the `lid` cache be
|
|
270
|
-
// one level up
|
|
271
|
-
keyOptions.lid.set(identifier.lid, identifier);
|
|
272
|
-
|
|
273
|
-
if (LOG_IDENTIFIERS) {
|
|
274
|
-
if (shouldGenerate) {
|
|
275
|
-
// eslint-disable-next-line no-console
|
|
276
|
-
console.log(`Identifiers: generated ${String(identifier)} for`, resource);
|
|
277
|
-
if (resource[DEBUG_IDENTIFIER_BUCKET]) {
|
|
278
|
-
// eslint-disable-next-line no-console
|
|
279
|
-
console.trace(
|
|
280
|
-
`[WARNING] Identifiers: generated a new identifier from a previously used identifier. This is likely a bug.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// populate our own secondary lookup table
|
|
288
|
-
// even for the "successful" secondary lookup
|
|
289
|
-
// by `_generate()`, since we missed the cache
|
|
290
|
-
// previously
|
|
291
|
-
// we use identifier.id instead of id here
|
|
292
|
-
// because they may not match and we prefer
|
|
293
|
-
// what we've set via resource data
|
|
294
|
-
if (identifier.id !== null) {
|
|
295
|
-
keyOptions.id.set(identifier.id, identifier);
|
|
296
|
-
|
|
297
|
-
// TODO allow filling out of `id` here
|
|
298
|
-
// for the `username` non-client created
|
|
299
|
-
// case.
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (LOG_IDENTIFIERS) {
|
|
305
|
-
if (!identifier && !shouldGenerate) {
|
|
306
|
-
// eslint-disable-next-line no-console
|
|
307
|
-
console.log(`Identifiers: cache MISS`, resource);
|
|
308
|
-
}
|
|
309
|
-
// eslint-disable-next-line no-console
|
|
310
|
-
console.groupEnd();
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return identifier;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* allows us to peek without generating when needed
|
|
318
|
-
* useful for the "create" case when we need to see if
|
|
319
|
-
* we are accidentally overwritting something
|
|
320
|
-
*
|
|
321
|
-
* @method peekRecordIdentifier
|
|
322
|
-
* @param resource
|
|
323
|
-
* @returns {StableRecordIdentifier | undefined}
|
|
324
|
-
* @private
|
|
325
|
-
*/
|
|
326
|
-
peekRecordIdentifier(resource: ResourceIdentifierObject | Identifier): StableRecordIdentifier | undefined {
|
|
327
|
-
return this._getRecordIdentifier(resource, false);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
Returns the Identifier for the given Resource, creates one if it does not yet exist.
|
|
332
|
-
|
|
333
|
-
Specifically this means that we:
|
|
334
|
-
|
|
335
|
-
- validate the `id` `type` and `lid` combo against known identifiers
|
|
336
|
-
- return an object with an `lid` that is stable (repeated calls with the same
|
|
337
|
-
`id` + `type` or `lid` will return the same `lid` value)
|
|
338
|
-
- this referential stability of the object itself is guaranteed
|
|
339
|
-
|
|
340
|
-
@method getOrCreateRecordIdentifier
|
|
341
|
-
@param resource
|
|
342
|
-
@returns {StableRecordIdentifier}
|
|
343
|
-
@public
|
|
344
|
-
*/
|
|
345
|
-
getOrCreateRecordIdentifier(resource: ResourceData | Identifier): StableRecordIdentifier {
|
|
346
|
-
return this._getRecordIdentifier(resource, true);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
Returns a new Identifier for the supplied data. Call this method to generate
|
|
351
|
-
an identifier when a new resource is being created local to the client and
|
|
352
|
-
potentially does not have an `id`.
|
|
353
|
-
|
|
354
|
-
Delegates generation to the user supplied `GenerateMethod` if one has been provided
|
|
355
|
-
with the signature `generateMethod({ type }, 'record')`.
|
|
356
|
-
|
|
357
|
-
@method createIdentifierForNewRecord
|
|
358
|
-
@param data
|
|
359
|
-
@returns {StableRecordIdentifier}
|
|
360
|
-
@public
|
|
361
|
-
*/
|
|
362
|
-
createIdentifierForNewRecord(data: { type: string; id?: string | null }): StableRecordIdentifier {
|
|
363
|
-
let newLid = this._generate(data, 'record');
|
|
364
|
-
let identifier = makeStableRecordIdentifier(data.id || null, data.type, newLid, 'record', true);
|
|
365
|
-
let keyOptions = getTypeIndex(this._cache.types, data.type);
|
|
366
|
-
|
|
367
|
-
// populate our unique table
|
|
368
|
-
if (DEBUG) {
|
|
369
|
-
if (this._cache.lids.has(identifier.lid)) {
|
|
370
|
-
throw new Error(`The lid generated for the new record is not unique as it matches an existing identifier`);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
this._cache.lids.set(identifier.lid, identifier);
|
|
374
|
-
|
|
375
|
-
// populate the type+lid cache
|
|
376
|
-
keyOptions.lid.set(newLid, identifier);
|
|
377
|
-
if (data.id) {
|
|
378
|
-
keyOptions.id.set(data.id, identifier);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (LOG_IDENTIFIERS) {
|
|
382
|
-
// eslint-disable-next-line no-console
|
|
383
|
-
console.log(`Identifiers: createded identifier ${String(identifier)} for newly generated resource`, data);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return identifier;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
Provides the opportunity to update secondary lookup tables for existing identifiers
|
|
391
|
-
Called after an identifier created with `createIdentifierForNewRecord` has been
|
|
392
|
-
committed.
|
|
393
|
-
|
|
394
|
-
Assigned `id` to an `Identifier` if `id` has not previously existed; however,
|
|
395
|
-
attempting to change the `id` or calling update without providing an `id` when
|
|
396
|
-
one is missing will throw an error.
|
|
397
|
-
|
|
398
|
-
- sets `id` (if `id` was previously `null`)
|
|
399
|
-
- `lid` and `type` MUST NOT be altered post creation
|
|
400
|
-
|
|
401
|
-
If a merge occurs, it is possible the returned identifier does not match the originally
|
|
402
|
-
provided identifier. In this case the abandoned identifier will go through the usual
|
|
403
|
-
`forgetRecordIdentifier` codepaths.
|
|
404
|
-
|
|
405
|
-
@method updateRecordIdentifier
|
|
406
|
-
@param identifierObject
|
|
407
|
-
@param data
|
|
408
|
-
@returns {StableRecordIdentifier}
|
|
409
|
-
@public
|
|
410
|
-
*/
|
|
411
|
-
updateRecordIdentifier(identifierObject: RecordIdentifier, data: ResourceData): StableRecordIdentifier {
|
|
412
|
-
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
|
|
413
|
-
|
|
414
|
-
let newId =
|
|
415
|
-
(data as ExistingResourceObject).id !== undefined ? coerceId((data as ExistingResourceObject).id) : null;
|
|
416
|
-
let existingIdentifier = detectMerge(this._cache.types, identifier, data, newId, this._cache.lids);
|
|
417
|
-
|
|
418
|
-
if (!existingIdentifier) {
|
|
419
|
-
// If the incoming type does not match the identifier type, we need to create an identifier for the incoming
|
|
420
|
-
// data so we can merge the incoming data with the existing identifier, see #7325 and #7363
|
|
421
|
-
if (
|
|
422
|
-
(data as ExistingResourceObject).type &&
|
|
423
|
-
identifier.type !== normalizeModelName((data as ExistingResourceObject).type)
|
|
424
|
-
) {
|
|
425
|
-
let incomingDataResource = { ...data };
|
|
426
|
-
// Need to strip the lid from the incomingData in order force a new identifier creation
|
|
427
|
-
delete incomingDataResource.lid;
|
|
428
|
-
existingIdentifier = this.getOrCreateRecordIdentifier(incomingDataResource);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (existingIdentifier) {
|
|
433
|
-
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
434
|
-
let generatedIdentifier = identifier;
|
|
435
|
-
identifier = this._mergeRecordIdentifiers(
|
|
436
|
-
keyOptions,
|
|
437
|
-
generatedIdentifier,
|
|
438
|
-
existingIdentifier,
|
|
439
|
-
data,
|
|
440
|
-
newId as string
|
|
441
|
-
);
|
|
442
|
-
if (LOG_IDENTIFIERS) {
|
|
443
|
-
// eslint-disable-next-line no-console
|
|
444
|
-
console.log(
|
|
445
|
-
`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`,
|
|
446
|
-
data
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
let id = identifier.id;
|
|
452
|
-
performRecordIdentifierUpdate(identifier, data, this._update);
|
|
453
|
-
newId = identifier.id;
|
|
454
|
-
|
|
455
|
-
// add to our own secondary lookup table
|
|
456
|
-
if (id !== newId && newId !== null) {
|
|
457
|
-
if (LOG_IDENTIFIERS) {
|
|
458
|
-
// eslint-disable-next-line no-console
|
|
459
|
-
console.log(
|
|
460
|
-
`Identifiers: updated id for identifier ${identifier.lid} from '${id}' to '${newId}' for resource`,
|
|
461
|
-
data
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
465
|
-
keyOptions.id.set(newId, identifier);
|
|
466
|
-
|
|
467
|
-
if (id !== null) {
|
|
468
|
-
keyOptions.id.delete(id);
|
|
469
|
-
}
|
|
470
|
-
} else if (LOG_IDENTIFIERS) {
|
|
471
|
-
// eslint-disable-next-line no-console
|
|
472
|
-
console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return identifier;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* @method _mergeRecordIdentifiers
|
|
480
|
-
* @private
|
|
481
|
-
*/
|
|
482
|
-
_mergeRecordIdentifiers(
|
|
483
|
-
keyOptions: KeyOptions,
|
|
484
|
-
identifier: StableRecordIdentifier,
|
|
485
|
-
existingIdentifier: StableRecordIdentifier,
|
|
486
|
-
data: ResourceIdentifierObject | ExistingResourceObject,
|
|
487
|
-
newId: string
|
|
488
|
-
): StableRecordIdentifier {
|
|
489
|
-
// delegate determining which identifier to keep to the configured MergeMethod
|
|
490
|
-
let kept = this._merge(identifier, existingIdentifier, data);
|
|
491
|
-
let abandoned = kept === identifier ? existingIdentifier : identifier;
|
|
492
|
-
|
|
493
|
-
// cleanup the identifier we no longer need
|
|
494
|
-
this.forgetRecordIdentifier(abandoned);
|
|
495
|
-
|
|
496
|
-
// ensure a secondary cache entry for this id for the identifier we do keep
|
|
497
|
-
keyOptions.id.set(newId, kept);
|
|
498
|
-
// ensure a secondary cache entry for this id for the abandoned identifier's type we do keep
|
|
499
|
-
let baseKeyOptions = getTypeIndex(this._cache.types, existingIdentifier.type);
|
|
500
|
-
baseKeyOptions.id.set(newId, kept);
|
|
501
|
-
|
|
502
|
-
// make sure that the `lid` on the data we are processing matches the lid we kept
|
|
503
|
-
data.lid = kept.lid;
|
|
504
|
-
|
|
505
|
-
return kept;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
Provides the opportunity to eliminate an identifier from secondary lookup tables
|
|
510
|
-
as well as eliminates it from ember-data's own lookup tables and book keeping.
|
|
511
|
-
|
|
512
|
-
Useful when a record has been deleted and the deletion has been persisted and
|
|
513
|
-
we do not care about the record anymore. Especially useful when an `id` of a
|
|
514
|
-
deleted record might be reused later for a new record.
|
|
515
|
-
|
|
516
|
-
@method forgetRecordIdentifier
|
|
517
|
-
@param identifierObject
|
|
518
|
-
@public
|
|
519
|
-
*/
|
|
520
|
-
forgetRecordIdentifier(identifierObject: RecordIdentifier): void {
|
|
521
|
-
let identifier = this.getOrCreateRecordIdentifier(identifierObject);
|
|
522
|
-
let keyOptions = getTypeIndex(this._cache.types, identifier.type);
|
|
523
|
-
if (identifier.id !== null) {
|
|
524
|
-
keyOptions.id.delete(identifier.id);
|
|
525
|
-
}
|
|
526
|
-
this._cache.lids.delete(identifier.lid);
|
|
527
|
-
keyOptions.lid.delete(identifier.lid);
|
|
528
|
-
|
|
529
|
-
IDENTIFIERS.delete(identifierObject);
|
|
530
|
-
this._forget(identifier, 'record');
|
|
531
|
-
if (LOG_IDENTIFIERS) {
|
|
532
|
-
// eslint-disable-next-line no-console
|
|
533
|
-
console.log(`Identifiers: released identifier ${identifierObject.lid}`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
destroy() {
|
|
538
|
-
this._reset();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function getTypeIndex(typeMap: TypeMap, type: string): KeyOptions {
|
|
543
|
-
let typeIndex: KeyOptions = typeMap[type];
|
|
544
|
-
|
|
545
|
-
if (typeIndex === undefined) {
|
|
546
|
-
typeIndex = {
|
|
547
|
-
lid: new Map(),
|
|
548
|
-
id: new Map(),
|
|
549
|
-
};
|
|
550
|
-
typeMap[type] = typeIndex;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return typeIndex;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function makeStableRecordIdentifier(
|
|
557
|
-
id: string | null,
|
|
558
|
-
type: string,
|
|
559
|
-
lid: string,
|
|
560
|
-
bucket: IdentifierBucket,
|
|
561
|
-
clientOriginated: boolean = false
|
|
562
|
-
): Readonly<StableRecordIdentifier> {
|
|
563
|
-
let recordIdentifier = {
|
|
564
|
-
lid,
|
|
565
|
-
id,
|
|
566
|
-
type,
|
|
567
|
-
};
|
|
568
|
-
IDENTIFIERS.add(recordIdentifier);
|
|
569
|
-
|
|
570
|
-
if (DEBUG) {
|
|
571
|
-
// we enforce immutability in dev
|
|
572
|
-
// but preserve our ability to do controlled updates to the reference
|
|
573
|
-
let wrapper = {
|
|
574
|
-
get lid() {
|
|
575
|
-
return recordIdentifier.lid;
|
|
576
|
-
},
|
|
577
|
-
get id() {
|
|
578
|
-
return recordIdentifier.id;
|
|
579
|
-
},
|
|
580
|
-
get type() {
|
|
581
|
-
return recordIdentifier.type;
|
|
582
|
-
},
|
|
583
|
-
toString() {
|
|
584
|
-
let { type, id, lid } = recordIdentifier;
|
|
585
|
-
return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${type}:${id} (${lid})`;
|
|
586
|
-
},
|
|
587
|
-
toJSON() {
|
|
588
|
-
let { type, id, lid } = recordIdentifier;
|
|
589
|
-
return { type, id, lid };
|
|
590
|
-
},
|
|
591
|
-
};
|
|
592
|
-
wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated;
|
|
593
|
-
wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket;
|
|
594
|
-
IDENTIFIERS.add(wrapper);
|
|
595
|
-
DEBUG_MAP.set(wrapper, recordIdentifier);
|
|
596
|
-
wrapper = freeze(wrapper);
|
|
597
|
-
return wrapper;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return recordIdentifier;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function performRecordIdentifierUpdate(identifier: StableRecordIdentifier, data: ResourceData, updateFn: UpdateMethod) {
|
|
604
|
-
if (DEBUG) {
|
|
605
|
-
let { lid } = data;
|
|
606
|
-
let id = 'id' in data ? data.id : undefined;
|
|
607
|
-
let type = 'type' in data && data.type && normalizeModelName(data.type);
|
|
608
|
-
|
|
609
|
-
// get the mutable instance behind our proxy wrapper
|
|
610
|
-
let wrapper = identifier;
|
|
611
|
-
identifier = DEBUG_MAP.get(wrapper);
|
|
612
|
-
|
|
613
|
-
if (lid !== undefined) {
|
|
614
|
-
let newLid = coerceId(lid);
|
|
615
|
-
if (newLid !== identifier.lid) {
|
|
616
|
-
throw new Error(
|
|
617
|
-
`The 'lid' for a RecordIdentifier cannot be updated once it has been created. Attempted to set lid for '${wrapper}' to '${lid}'.`
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (id !== undefined) {
|
|
623
|
-
let newId = coerceId(id);
|
|
624
|
-
|
|
625
|
-
if (identifier.id !== null && identifier.id !== newId) {
|
|
626
|
-
// here we warn and ignore, as this may be a mistake, but we allow the user
|
|
627
|
-
// to have multiple cache-keys pointing at a single lid so we cannot error
|
|
628
|
-
warn(
|
|
629
|
-
`The 'id' for a RecordIdentifier should not be updated once it has been set. Attempted to set id for '${wrapper}' to '${newId}'.`,
|
|
630
|
-
false,
|
|
631
|
-
{ id: 'ember-data:multiple-ids-for-identifier' }
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// TODO consider just ignoring here to allow flexible polymorphic support
|
|
637
|
-
if (type && type !== identifier.type) {
|
|
638
|
-
throw new Error(
|
|
639
|
-
`The 'type' for a RecordIdentifier cannot be updated once it has been set. Attempted to set type for '${wrapper}' to '${type}'.`
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
updateFn(wrapper, data, 'record');
|
|
644
|
-
} else {
|
|
645
|
-
updateFn(identifier, data, 'record');
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// upgrade the ID, this is a "one time only" ability
|
|
649
|
-
// for the multiple-cache-key scenario we "could"
|
|
650
|
-
// use a heuristic to guess the best id for display
|
|
651
|
-
// (usually when `data.id` is available and `data.attributes` is not)
|
|
652
|
-
if ((data as ExistingResourceObject).id !== undefined) {
|
|
653
|
-
identifier.id = coerceId((data as ExistingResourceObject).id);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function detectMerge(
|
|
658
|
-
typesCache: ConfidentDict<KeyOptions>,
|
|
659
|
-
identifier: StableRecordIdentifier,
|
|
660
|
-
data: ResourceIdentifierObject | ExistingResourceObject,
|
|
661
|
-
newId: string | null,
|
|
662
|
-
lids: IdentifierMap
|
|
663
|
-
): StableRecordIdentifier | false {
|
|
664
|
-
const { id, type, lid } = identifier;
|
|
665
|
-
if (id !== null && id !== newId && newId !== null) {
|
|
666
|
-
let keyOptions = getTypeIndex(typesCache, identifier.type);
|
|
667
|
-
let existingIdentifier = keyOptions.id.get(newId);
|
|
668
|
-
|
|
669
|
-
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
670
|
-
} else {
|
|
671
|
-
let newType = (data as ExistingResourceObject).type && normalizeModelName((data as ExistingResourceObject).type);
|
|
672
|
-
|
|
673
|
-
// If the ids and type are the same but lid is not the same, we should trigger a merge of the identifiers
|
|
674
|
-
if (id !== null && id === newId && newType === type && data.lid && data.lid !== lid) {
|
|
675
|
-
let existingIdentifier = lids.get(data.lid);
|
|
676
|
-
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
677
|
-
// If the lids are the same, and ids are the same, but types are different we should trigger a merge of the identifiers
|
|
678
|
-
} else if (id !== null && id === newId && newType && newType !== type && data.lid && data.lid === lid) {
|
|
679
|
-
let keyOptions = getTypeIndex(typesCache, newType);
|
|
680
|
-
let existingIdentifier = keyOptions.id.get(id);
|
|
681
|
-
return existingIdentifier !== undefined ? existingIdentifier : false;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
return false;
|
|
686
|
-
}
|