@ember-data/store 4.8.0-alpha.5 → 4.9.0-alpha.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.
@@ -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`.
@@ -29,18 +31,38 @@ if (DEBUG) {
29
31
  };
30
32
 
31
33
  assertPolymorphicType = function assertPolymorphicType(parentIdentifier, parentDefinition, addedIdentifier, store) {
32
- store = store._store ? store._store : store; // allow usage with storeWrapper
33
- let addedModelName = addedIdentifier.type;
34
- let parentModelName = parentIdentifier.type;
35
- let key = parentDefinition.key;
36
- let relationshipModelName = parentDefinition.type;
37
- let relationshipClass = store.modelFor(relationshipModelName);
38
- let addedClass = store.modelFor(addedModelName);
39
-
40
- 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.`;
41
- let isPolymorphic = checkPolymorphic(relationshipClass, addedClass);
42
-
43
- assert(assertionMessage, isPolymorphic);
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,7 +4,7 @@
4
4
  import { assert, warn } from '@ember/debug';
5
5
  import { DEBUG } from '@glimmer/env';
6
6
 
7
- import { getOwnConfig, importSync, macroCondition } from '@embroider/macros';
7
+ import { getOwnConfig, macroCondition } from '@embroider/macros';
8
8
 
9
9
  import { LOG_IDENTIFIERS } from '@ember-data/private-build-infra/debugging';
10
10
  import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
@@ -25,6 +25,7 @@ import coerceId from '../utils/coerce-id';
25
25
  import { DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '../utils/identifer-debug-consts';
26
26
  import isNonEmptyString from '../utils/is-non-empty-string';
27
27
  import normalizeModelName from '../utils/normalize-model-name';
28
+ import installPolyfill from '../utils/uuid-polyfill';
28
29
 
29
30
  const IDENTIFIERS = new Set();
30
31
 
@@ -36,7 +37,7 @@ const isFastBoot = typeof FastBoot !== 'undefined';
36
37
  const _crypto: Crypto = isFastBoot ? (FastBoot.require('crypto') as Crypto) : window.crypto;
37
38
 
38
39
  if (macroCondition(getOwnConfig<{ polyfillUUID: boolean }>().polyfillUUID)) {
39
- importSync('./utils/uuid-polyfill');
40
+ installPolyfill();
40
41
  }
41
42
 
42
43
  function uuidv4(): string {
@@ -4,11 +4,11 @@ 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';
11
- import type { Graph, peekGraph } from '@ember-data/record-data/-private/graph/index';
9
+ import { DEPRECATE_V1_RECORD_DATA, DEPRECATE_V1CACHE_STORE_APIS } from '@ember-data/private-build-infra/deprecations';
10
+ import type { Graph } from '@ember-data/record-data/-private/graph/graph';
11
+ import type { peekGraph } from '@ember-data/record-data/-private/graph/index';
12
12
  import type {
13
13
  ExistingResourceIdentifierObject,
14
14
  ExistingResourceObject,
@@ -129,44 +129,6 @@ export class InstanceCache {
129
129
  reference: new WeakMap<StableRecordIdentifier, RecordReference>(),
130
130
  };
131
131
 
132
- recordIsLoaded(identifier: StableRecordIdentifier, filterDeleted: boolean = false) {
133
- const recordData = this.__instances.recordData.get(identifier);
134
- if (!recordData) {
135
- return false;
136
- }
137
- const isNew = recordData.isNew(identifier);
138
- const isEmpty = recordData.isEmpty(identifier);
139
-
140
- // if we are new we must consider ourselves loaded
141
- if (isNew) {
142
- return !recordData.isDeleted(identifier);
143
- }
144
- // even if we have a past request, if we are now empty we are not loaded
145
- // typically this is true after an unloadRecord call
146
-
147
- // if we are not empty, not new && we have a fulfilled request then we are loaded
148
- // we should consider allowing for something to be loaded that is simply "not empty".
149
- // which is how RecordState currently handles this case; however, RecordState is buggy
150
- // in that it does not account for unloading.
151
- return filterDeleted && recordData.isDeletionCommitted(identifier) ? false : !isEmpty;
152
-
153
- /*
154
- const req = this.store.getRequestStateService();
155
- const fulfilled = req.getLastRequestForRecord(identifier);
156
- const isLocallyLoaded = !isEmpty;
157
- const isLoading =
158
- !isLocallyLoaded &&
159
- fulfilled === null &&
160
- req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query');
161
-
162
- if (isEmpty || (filterDeleted && recordData.isDeletionCommitted(identifier)) || isLoading) {
163
- return false;
164
- }
165
-
166
- return true;
167
- */
168
- }
169
-
170
132
  constructor(store: Store) {
171
133
  this.store = store;
172
134
 
@@ -179,25 +141,24 @@ export class InstanceCache {
179
141
 
180
142
  store.identifierCache.__configureMerge(
181
143
  (identifier: StableRecordIdentifier, matchedIdentifier: StableRecordIdentifier, resourceData) => {
182
- let intendedIdentifier = identifier;
144
+ let keptIdentifier = identifier;
183
145
  if (identifier.id !== matchedIdentifier.id) {
184
- intendedIdentifier =
185
- 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier;
146
+ keptIdentifier = 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier;
186
147
  } else if (identifier.type !== matchedIdentifier.type) {
187
- intendedIdentifier =
148
+ keptIdentifier =
188
149
  'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier;
189
150
  }
190
- let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier;
151
+ let staleIdentifier = identifier === keptIdentifier ? matchedIdentifier : identifier;
191
152
 
192
153
  // check for duplicate entities
193
- let imHasRecord = this.__instances.record.has(intendedIdentifier);
194
- let otherHasRecord = this.__instances.record.has(altIdentifier);
195
- let imRecordData = this.__instances.recordData.get(intendedIdentifier) || null;
196
- let otherRecordData = this.__instances.recordData.get(altIdentifier) || null;
154
+ let keptHasRecord = this.__instances.record.has(keptIdentifier);
155
+ let staleHasRecord = this.__instances.record.has(staleIdentifier);
156
+ let keptRecordData = this.__instances.recordData.get(keptIdentifier) || null;
157
+ let staleRecordData = this.__instances.recordData.get(staleIdentifier) || null;
197
158
 
198
159
  // we cannot merge entities when both have records
199
160
  // (this may not be strictly true, we could probably swap the recordData the record points at)
200
- if (imHasRecord && otherHasRecord) {
161
+ if (keptHasRecord && staleHasRecord) {
201
162
  // TODO we probably don't need to throw these errors anymore
202
163
  // we can probably just "swap" what data source the abandoned
203
164
  // record points at so long as
@@ -211,7 +172,7 @@ export class InstanceCache {
211
172
  }:${String(matchedIdentifier.id)} (${matchedIdentifier.lid})'`
212
173
  );
213
174
  }
214
- // TODO @runspired determine when this is even possible
175
+
215
176
  assert(
216
177
  `Failed to update the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${
217
178
  identifier.lid
@@ -221,33 +182,26 @@ export class InstanceCache {
221
182
  );
222
183
  }
223
184
 
224
- // remove "other" from cache
225
- if (otherHasRecord) {
226
- // TODO probably need to release other things
185
+ let recordData = keptRecordData || staleRecordData;
186
+
187
+ if (recordData) {
188
+ recordData.sync({
189
+ op: 'mergeIdentifiers',
190
+ record: staleIdentifier,
191
+ value: keptIdentifier,
192
+ });
193
+ } else if (HAS_RECORD_DATA_PACKAGE) {
194
+ // TODO notify cache always, this requires it always being a singleton
195
+ // and not ever specific to one record-data
196
+ this.store.__private_singleton_recordData?.sync({
197
+ op: 'mergeIdentifiers',
198
+ record: staleIdentifier,
199
+ value: keptIdentifier,
200
+ });
227
201
  }
228
202
 
229
- if (imRecordData === null && otherRecordData === null) {
230
- // nothing more to do
231
- return intendedIdentifier;
232
-
233
- // only the other has a RecordData
234
- // OR only the other has a Record
235
- } else if (
236
- (imRecordData === null && otherRecordData !== null) ||
237
- (imRecordData && !imHasRecord && otherRecordData && otherHasRecord)
238
- ) {
239
- if (imRecordData) {
240
- // TODO check if we are retained in any async relationships
241
- // TODO probably need to release other things
242
- // im.destroy();
243
- }
244
- imRecordData = otherRecordData!;
245
- // TODO do we need to notify the id change?
246
- // TODO swap recordIdentifierFor result?
247
-
248
- // just use im
249
- } else {
250
- // otherIm.destroy();
203
+ if (staleRecordData === null) {
204
+ return keptIdentifier;
251
205
  }
252
206
 
253
207
  /*
@@ -260,7 +214,8 @@ export class InstanceCache {
260
214
  }
261
215
  */
262
216
 
263
- return intendedIdentifier;
217
+ this.unloadRecord(staleIdentifier);
218
+ return keptIdentifier;
264
219
  }
265
220
  );
266
221
  }
@@ -324,23 +279,23 @@ export class InstanceCache {
324
279
  identifier.lid,
325
280
  this._storeWrapper
326
281
  );
327
- if (V2CACHE_SINGLETON_MANAGER) {
282
+ if (DEPRECATE_V1_RECORD_DATA) {
283
+ recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
284
+ } else {
328
285
  recordData = this.__cacheManager =
329
286
  this.__cacheManager || new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
330
- } else {
331
- recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
332
287
  }
333
288
  } else {
334
289
  let recordDataInstance = this.store.createRecordDataFor(identifier, this._storeWrapper);
335
- if (V2CACHE_SINGLETON_MANAGER) {
290
+ if (DEPRECATE_V1_RECORD_DATA) {
291
+ recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
292
+ } else {
336
293
  if (DEBUG) {
337
- recordData = this.__cacheManager = this.__cacheManager || new SingletonRecordDataManager(this.store);
294
+ recordData = this.__cacheManager = this.__cacheManager || new SingletonRecordDataManager();
338
295
  (recordData as SingletonRecordDataManager)._addRecordData(identifier, recordDataInstance as RecordData);
339
296
  } else {
340
297
  recordData = recordDataInstance as RecordData;
341
298
  }
342
- } else {
343
- recordData = new NonSingletonRecordDataManager(this.store, recordDataInstance, identifier);
344
299
  }
345
300
  }
346
301
  setRecordDataFor(identifier, recordData);
@@ -366,6 +321,44 @@ export class InstanceCache {
366
321
  return reference;
367
322
  }
368
323
 
324
+ recordIsLoaded(identifier: StableRecordIdentifier, filterDeleted: boolean = false) {
325
+ const recordData = this.__instances.recordData.get(identifier);
326
+ if (!recordData) {
327
+ return false;
328
+ }
329
+ const isNew = recordData.isNew(identifier);
330
+ const isEmpty = recordData.isEmpty(identifier);
331
+
332
+ // if we are new we must consider ourselves loaded
333
+ if (isNew) {
334
+ return !recordData.isDeleted(identifier);
335
+ }
336
+ // even if we have a past request, if we are now empty we are not loaded
337
+ // typically this is true after an unloadRecord call
338
+
339
+ // if we are not empty, not new && we have a fulfilled request then we are loaded
340
+ // we should consider allowing for something to be loaded that is simply "not empty".
341
+ // which is how RecordState currently handles this case; however, RecordState is buggy
342
+ // in that it does not account for unloading.
343
+ return filterDeleted && recordData.isDeletionCommitted(identifier) ? false : !isEmpty;
344
+
345
+ /*
346
+ const req = this.store.getRequestStateService();
347
+ const fulfilled = req.getLastRequestForRecord(identifier);
348
+ const isLocallyLoaded = !isEmpty;
349
+ const isLoading =
350
+ !isLocallyLoaded &&
351
+ fulfilled === null &&
352
+ req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query');
353
+
354
+ if (isEmpty || (filterDeleted && recordData.isDeletionCommitted(identifier)) || isLoading) {
355
+ return false;
356
+ }
357
+
358
+ return true;
359
+ */
360
+ }
361
+
369
362
  createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot {
370
363
  return new Snapshot(options, identifier, this.store);
371
364
  }
@@ -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 { PromiseArray, PromiseObject, deprecatedPromiseObject } from './proxies/promise-proxies';
45
-
46
- export { default as RecordArray } from './record-arrays/record-array';
47
- export { default as AdapterPopulatedRecordArray } from './record-arrays/adapter-populated-record-array';
48
-
49
- export { default as RecordArrayManager } from './managers/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';
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);