@ember-data/store 4.8.0-alpha.2 → 4.8.0-alpha.3

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.
Files changed (36) hide show
  1. package/addon/-debug/index.js +1 -1
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +83 -8
  3. package/addon/-private/caches/instance-cache.ts +759 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -2
  5. package/addon/-private/index.ts +12 -15
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +3 -3
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +4 -5
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +2 -2
  9. package/addon/-private/{record-array-manager.ts → managers/record-array-manager.ts} +16 -44
  10. package/addon/-private/{record-data-store-wrapper.ts → managers/record-data-store-wrapper.ts} +34 -56
  11. package/addon/-private/managers/record-notification-manager.ts +96 -0
  12. package/addon/-private/{fetch-manager.ts → network/fetch-manager.ts} +18 -16
  13. package/addon/-private/{finders.js → network/finders.js} +4 -12
  14. package/addon/-private/{request-cache.ts → network/request-cache.ts} +0 -0
  15. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +3 -3
  16. package/addon/-private/{snapshot.ts → network/snapshot.ts} +24 -31
  17. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +4 -4
  18. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  19. package/addon/-private/record-arrays/adapter-populated-record-array.ts +9 -11
  20. package/addon/-private/record-arrays/record-array.ts +16 -14
  21. package/addon/-private/{core-store.ts → store-service.ts} +186 -127
  22. package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
  23. package/addon/-private/{common.js → utils/common.js} +1 -2
  24. package/addon/-private/utils/construct-resource.ts +2 -2
  25. package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
  26. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +0 -0
  27. package/addon/-private/utils/promise-record.ts +3 -3
  28. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  29. package/addon/-private/{weak-cache.ts → utils/weak-cache.ts} +0 -0
  30. package/package.json +5 -5
  31. package/addon/-private/identity-map.ts +0 -54
  32. package/addon/-private/instance-cache.ts +0 -387
  33. package/addon/-private/internal-model-factory.ts +0 -359
  34. package/addon/-private/internal-model-map.ts +0 -121
  35. package/addon/-private/model/internal-model.ts +0 -600
  36. package/addon/-private/record-notification-manager.ts +0 -73
@@ -5,7 +5,7 @@ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
5
5
  import type { RecordData } from '@ember-data/types/q/record-data';
6
6
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
7
7
 
8
- import WeakCache from './weak-cache';
8
+ import WeakCache from '../utils/weak-cache';
9
9
 
10
10
  /*
11
11
  * Returns the RecordData instance associated with a given
@@ -24,7 +24,7 @@ export function setRecordDataFor(identifier: StableRecordIdentifier | RecordInst
24
24
  RecordDataForIdentifierCache.set(identifier, recordData);
25
25
  }
26
26
 
27
- export function removeRecordDataFor(identifier: StableRecordIdentifier): void {
27
+ export function removeRecordDataFor(identifier: StableRecordIdentifier | RecordInstance): void {
28
28
  RecordDataForIdentifierCache.delete(identifier);
29
29
  }
30
30
 
@@ -6,19 +6,19 @@ import { assert, deprecate } from '@ember/debug';
6
6
 
7
7
  import { DEPRECATE_HELPERS } from '@ember-data/private-build-infra/deprecations';
8
8
 
9
- import _normalize from './normalize-model-name';
9
+ import _normalize from './utils/normalize-model-name';
10
10
 
11
- export { default as Store, storeFor } from './core-store';
11
+ export { default as Store, storeFor } from './store-service';
12
12
 
13
- export { recordIdentifierFor } from './internal-model-factory';
13
+ export { recordIdentifierFor } from './caches/instance-cache';
14
14
 
15
- export { default as Snapshot } from './snapshot';
15
+ export { default as Snapshot } from './network/snapshot';
16
16
  export {
17
17
  setIdentifierGenerationMethod,
18
18
  setIdentifierUpdateMethod,
19
19
  setIdentifierForgetMethod,
20
20
  setIdentifierResetMethod,
21
- } from './identifier-cache';
21
+ } from './caches/identifier-cache';
22
22
 
23
23
  export function normalizeModelName(modelName: string) {
24
24
  if (DEPRECATE_HELPERS) {
@@ -37,23 +37,20 @@ export function normalizeModelName(modelName: string) {
37
37
  assert(`normalizeModelName support has been removed`);
38
38
  }
39
39
 
40
- export { default as coerceId } from './coerce-id';
40
+ export { default as coerceId } from './utils/coerce-id';
41
41
 
42
- // `ember-data-model-fragments` relies on `InternalModel`
43
- export { default as InternalModel } from './model/internal-model';
44
-
45
- export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './promise-proxies';
42
+ export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './proxies/promise-proxies';
46
43
 
47
44
  export { default as RecordArray } from './record-arrays/record-array';
48
45
  export { default as AdapterPopulatedRecordArray } from './record-arrays/adapter-populated-record-array';
49
46
 
50
- export { default as RecordArrayManager } from './record-array-manager';
47
+ export { default as RecordArrayManager } from './managers/record-array-manager';
51
48
 
52
49
  // // Used by tests
53
- export { default as SnapshotRecordArray } from './snapshot-record-array';
50
+ export { default as SnapshotRecordArray } from './network/snapshot-record-array';
54
51
 
55
52
  // New
56
- export { default as recordDataFor, removeRecordDataFor } from './record-data-for';
57
- export { default as RecordDataStoreWrapper } from './record-data-store-wrapper';
53
+ export { default as recordDataFor, removeRecordDataFor } from './caches/record-data-for';
54
+ export { default as RecordDataStoreWrapper } from './managers/record-data-store-wrapper';
58
55
 
59
- export { default as WeakCache } from './weak-cache';
56
+ export { default as WeakCache } from './utils/weak-cache';
@@ -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 Store from '../core-store';
14
- import type { NotificationType } from '../record-notification-manager';
15
- import { unsubscribe } from '../record-notification-manager';
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
@@ -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 './core-store';
14
- import normalizeModelName from './normalize-model-name';
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
 
@@ -55,7 +54,7 @@ export class DSModelSchemaDefinitionService {
55
54
 
56
55
  if (attributes === undefined) {
57
56
  let modelClass = this.store.modelFor(modelName);
58
- let attributeMap = get(modelClass, 'attributes');
57
+ let attributeMap = modelClass.attributes;
59
58
 
60
59
  attributes = Object.create(null);
61
60
  attributeMap.forEach((meta, name) => (attributes[name] = meta));
@@ -88,7 +87,7 @@ export class DSModelSchemaDefinitionService {
88
87
 
89
88
  if (relationships === undefined) {
90
89
  let modelClass = this.store.modelFor(modelName);
91
- relationships = get(modelClass, 'relationshipsObject') || null;
90
+ relationships = modelClass.relationshipsObject || null;
92
91
  this._relationshipsDefCache[modelName] = relationships;
93
92
  }
94
93
 
@@ -4,8 +4,8 @@ import type { ModelSchema } from '@ember-data/types/q/ds-model';
4
4
  import type { AttributeSchema, RelationshipSchema } from '@ember-data/types/q/record-data-schemas';
5
5
  import type { Dict } from '@ember-data/types/q/utils';
6
6
 
7
- import type Store from '../core-store';
8
- import WeakCache from '../weak-cache';
7
+ import type Store from '../store-service';
8
+ import WeakCache from '../utils/weak-cache';
9
9
 
10
10
  const AvailableShims = new WeakCache<Store, Dict<ShimModelClass>>(DEBUG ? 'schema-shims' : '');
11
11
  AvailableShims._generator = () => {
@@ -4,20 +4,17 @@
4
4
 
5
5
  import { A } from '@ember/array';
6
6
  import { assert } from '@ember/debug';
7
- import { set } from '@ember/object';
8
7
  import { _backburner as emberBackburner } from '@ember/runloop';
9
8
  import { DEBUG } from '@glimmer/env';
10
9
 
11
- // import isStableIdentifier from '../identifiers/is-stable-identifier';
12
10
  import type { CollectionResourceDocument, Meta } from '@ember-data/types/q/ember-data-json-api';
13
11
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
14
12
  import type { Dict } from '@ember-data/types/q/utils';
15
13
 
16
- import type Store from './core-store';
17
- import { internalModelFactoryFor } from './internal-model-factory';
18
- import AdapterPopulatedRecordArray from './record-arrays/adapter-populated-record-array';
19
- import RecordArray from './record-arrays/record-array';
20
- import WeakCache from './weak-cache';
14
+ import AdapterPopulatedRecordArray from '../record-arrays/adapter-populated-record-array';
15
+ import RecordArray from '../record-arrays/record-array';
16
+ import type Store from '../store-service';
17
+ import WeakCache from '../utils/weak-cache';
21
18
 
22
19
  const RecordArraysCache = new WeakCache<StableRecordIdentifier, Set<RecordArray>>(DEBUG ? 'record-arrays' : '');
23
20
  RecordArraysCache._generator = () => new Set();
@@ -27,27 +24,6 @@ export function recordArraysForIdentifier(identifier: StableRecordIdentifier): S
27
24
 
28
25
  const pendingForIdentifier: Set<StableRecordIdentifier> = new Set([]);
29
26
 
30
- function getIdentifier(identifier: StableRecordIdentifier): StableRecordIdentifier {
31
- // during dematerialization we will get an identifier that
32
- // has already been removed from the identifiers cache
33
- // so it will not behave as if stable. This is a bug we should fix.
34
- // if (!isStableIdentifier(identifierOrInternalModel)) {
35
- // console.log({ unstable: i });
36
- // }
37
-
38
- return identifier;
39
- }
40
-
41
- function shouldIncludeInRecordArrays(store: Store, identifier: StableRecordIdentifier): boolean {
42
- const cache = internalModelFactoryFor(store);
43
- const internalModel = cache.peek(identifier);
44
-
45
- if (internalModel === null) {
46
- return false;
47
- }
48
- return !internalModel.isHiddenFromRecordArrays();
49
- }
50
-
51
27
  /**
52
28
  @class RecordArrayManager
53
29
  @internal
@@ -84,6 +60,7 @@ class RecordArrayManager {
84
60
  return;
85
61
  }
86
62
  let identifiersToRemove: StableRecordIdentifier[] = [];
63
+ let cache = this.store._instanceCache;
87
64
 
88
65
  for (let j = 0; j < identifiers.length; j++) {
89
66
  let i = identifiers[j];
@@ -91,8 +68,7 @@ class RecordArrayManager {
91
68
  // recordArrayManager
92
69
  pendingForIdentifier.delete(i);
93
70
  // build up a set of models to ensure we have purged correctly;
94
- let isIncluded = shouldIncludeInRecordArrays(this.store, i);
95
- if (!isIncluded) {
71
+ if (!cache.recordIsLoaded(i, true)) {
96
72
  identifiersToRemove.push(i);
97
73
  }
98
74
  }
@@ -130,8 +106,8 @@ class RecordArrayManager {
130
106
  return;
131
107
  }
132
108
  let hasNoPotentialDeletions = pending.length === 0;
133
- let map = internalModelFactoryFor(this.store).modelMapFor(modelName);
134
- let hasNoInsertionsOrRemovals = map.length === array.length;
109
+ let listSize = this.store._instanceCache.peekList[modelName]?.size;
110
+ let hasNoInsertionsOrRemovals = listSize === array.length;
135
111
 
136
112
  /*
137
113
  Ideally the recordArrayManager has knowledge of the changes to be applied to
@@ -165,7 +141,7 @@ class RecordArrayManager {
165
141
  _didUpdateAll(modelName: string): void {
166
142
  let recordArray = this._liveRecordArrays[modelName];
167
143
  if (recordArray) {
168
- set(recordArray, 'isUpdating', false);
144
+ recordArray.isUpdating = false;
169
145
  // TODO potentially we should sync here, currently
170
146
  // this occurs as a side-effect of individual records updating
171
147
  // this._syncLiveRecordArray(recordArray, modelName);
@@ -204,13 +180,14 @@ class RecordArrayManager {
204
180
  }
205
181
 
206
182
  _visibleIdentifiersByType(modelName: string) {
207
- let all = internalModelFactoryFor(this.store).modelMapFor(modelName).recordIdentifiers;
183
+ const cache = this.store._instanceCache;
184
+ const list = cache.peekList[modelName];
185
+ let all = list ? [...list.values()] : [];
208
186
  let visible: StableRecordIdentifier[] = [];
209
187
  for (let i = 0; i < all.length; i++) {
210
188
  let identifier = all[i];
211
- let shouldInclude = shouldIncludeInRecordArrays(this.store, identifier);
212
189
 
213
- if (shouldInclude) {
190
+ if (cache.recordIsLoaded(identifier, true)) {
214
191
  visible.push(identifier);
215
192
  }
216
193
  }
@@ -335,7 +312,6 @@ class RecordArrayManager {
335
312
  _associateWithRecordArray(identifiers: StableRecordIdentifier[], array: RecordArray): void {
336
313
  for (let i = 0, l = identifiers.length; i < l; i++) {
337
314
  let identifier = identifiers[i];
338
- identifier = getIdentifier(identifier);
339
315
  let recordArrays = this.getRecordArraysForIdentifier(identifier);
340
316
  recordArrays.add(array);
341
317
  }
@@ -350,7 +326,6 @@ class RecordArrayManager {
350
326
  return;
351
327
  }
352
328
  let modelName = identifier.type;
353
- identifier = getIdentifier(identifier);
354
329
 
355
330
  if (pendingForIdentifier.has(identifier)) {
356
331
  return;
@@ -397,20 +372,18 @@ function removeFromArray(array: RecordArray[], item: RecordArray): boolean {
397
372
  function updateLiveRecordArray(store: Store, recordArray: RecordArray, identifiers: StableRecordIdentifier[]): void {
398
373
  let identifiersToAdd: StableRecordIdentifier[] = [];
399
374
  let identifiersToRemove: StableRecordIdentifier[] = [];
375
+ const cache = store._instanceCache;
400
376
 
401
377
  for (let i = 0; i < identifiers.length; i++) {
402
378
  let identifier = identifiers[i];
403
- let shouldInclude = shouldIncludeInRecordArrays(store, identifier);
404
379
  let recordArrays = recordArraysForIdentifier(identifier);
405
380
 
406
- if (shouldInclude) {
381
+ if (cache.recordIsLoaded(identifier, true)) {
407
382
  if (!recordArrays.has(recordArray)) {
408
383
  identifiersToAdd.push(identifier);
409
384
  recordArrays.add(recordArray);
410
385
  }
411
- }
412
-
413
- if (!shouldInclude) {
386
+ } else {
414
387
  identifiersToRemove.push(identifier);
415
388
  recordArrays.delete(recordArray);
416
389
  }
@@ -431,7 +404,6 @@ function removeFromAdapterPopulatedRecordArrays(store: Store, identifiers: Stabl
431
404
  }
432
405
 
433
406
  function removeFromAll(store: Store, identifier: StableRecordIdentifier): void {
434
- identifier = getIdentifier(identifier);
435
407
  const recordArrays = recordArraysForIdentifier(identifier);
436
408
 
437
409
  recordArrays.forEach(function (recordArray) {
@@ -1,7 +1,4 @@
1
- import { importSync } from '@embroider/macros';
2
-
3
1
  import type { RelationshipDefinition } from '@ember-data/model/-private/relationship-meta';
4
- import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
5
2
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
6
3
  import type { RecordData } from '@ember-data/types/q/record-data';
7
4
  import type {
@@ -11,10 +8,9 @@ import type {
11
8
  } from '@ember-data/types/q/record-data-schemas';
12
9
  import type { RecordDataStoreWrapper as StoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
13
10
 
14
- import type Store from './core-store';
15
- import type { IdentifierCache } from './identifier-cache';
16
- import { internalModelFactoryFor } from './internal-model-factory';
17
- import constructResource from './utils/construct-resource';
11
+ import type { IdentifierCache } from '../caches/identifier-cache';
12
+ import type Store from '../store-service';
13
+ import constructResource from '../utils/construct-resource';
18
14
 
19
15
  /**
20
16
  @module @ember-data/store
@@ -24,17 +20,6 @@ function metaIsRelationshipDefinition(meta: RelationshipSchema): meta is Relatio
24
20
  return typeof (meta as RelationshipDefinition)._inverseKey === 'function';
25
21
  }
26
22
 
27
- let peekGraph;
28
- if (HAS_RECORD_DATA_PACKAGE) {
29
- let _peekGraph;
30
- peekGraph = (wrapper) => {
31
- _peekGraph =
32
- _peekGraph ||
33
- (importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')).peekGraph;
34
- return _peekGraph(wrapper);
35
- };
36
- }
37
-
38
23
  export default class RecordDataStoreWrapper implements StoreWrapper {
39
24
  declare _willNotify: boolean;
40
25
  declare _pendingNotifies: Map<StableRecordIdentifier, Map<string, string>>;
@@ -86,19 +71,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
86
71
  let pending = this._pendingNotifies;
87
72
  this._pendingNotifies = new Map();
88
73
  this._willNotify = false;
89
- const factory = internalModelFactoryFor(this._store);
90
74
 
91
75
  pending.forEach((map, identifier) => {
92
- const internalModel = factory.peek(identifier);
93
- if (internalModel) {
94
- map.forEach((kind, key) => {
95
- if (kind === 'belongsTo') {
96
- internalModel.notifyBelongsToChange(key);
97
- } else {
98
- internalModel.notifyHasManyChange(key);
99
- }
100
- });
101
- }
76
+ map.forEach((kind, key) => {
77
+ this._store._notificationManager.notify(identifier, 'relationships', key);
78
+ });
102
79
  });
103
80
  }
104
81
 
@@ -152,11 +129,8 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
152
129
  notifyPropertyChange(type: string, id: string | null, lid: string | null | undefined, key?: string): void {
153
130
  const resource = constructResource(type, id, lid);
154
131
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
155
- let internalModel = internalModelFactoryFor(this._store).peek(identifier);
156
132
 
157
- if (internalModel) {
158
- internalModel.notifyAttributes(key ? [key] : []);
159
- }
133
+ this._store._notificationManager.notify(identifier, 'attributes', key);
160
134
  }
161
135
 
162
136
  notifyHasManyChange(type: string, id: string | null, lid: string, key: string): void;
@@ -181,10 +155,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
181
155
  notifyStateChange(type: string, id: string | null, lid: string | null, key?: string): void {
182
156
  const resource = constructResource(type, id, lid);
183
157
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
184
- let internalModel = internalModelFactoryFor(this._store).peek(identifier);
185
158
 
186
- if (internalModel) {
187
- internalModel.notifyStateChange(key);
159
+ this._store._notificationManager.notify(identifier, 'state');
160
+
161
+ if (!key || key === 'isDeletionCommitted') {
162
+ this._store.recordArrayManager.recordDidChange(identifier);
188
163
  }
189
164
  }
190
165
 
@@ -192,17 +167,25 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
192
167
  recordDataFor(type: string, id: string | null, lid: string): RecordData;
193
168
  recordDataFor(type: string): RecordData;
194
169
  recordDataFor(type: string, id?: string | null, lid?: string | null): RecordData {
195
- let identifier: StableRecordIdentifier | { type: string };
196
- let isCreate: boolean = false;
170
+ // TODO @deprecate create capability. This is problematic because there's
171
+ // no outside association between this RecordData and an Identifier. It's
172
+ // likely a mistake but we said in an RFC we'd allow this. We should RFC
173
+ // enforcing someone to use the record-data and identifier-cache APIs to
174
+ // create a new identifier and then call clientDidCreate on the RecordData
175
+ // instead.
176
+ const identifier =
177
+ !id && !lid
178
+ ? this.identifierCache.createIdentifierForNewRecord({ type: type })
179
+ : this.identifierCache.getOrCreateRecordIdentifier(constructResource(type, id, lid));
180
+
181
+ const recordData = this._store._instanceCache.getRecordData(identifier);
182
+
197
183
  if (!id && !lid) {
198
- isCreate = true;
199
- identifier = { type };
200
- } else {
201
- const resource = constructResource(type, id, lid);
202
- identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
184
+ recordData.clientDidCreate();
185
+ this._store.recordArrayManager.recordDidChange(identifier);
203
186
  }
204
187
 
205
- return this._store._instanceCache.recordDataFor(identifier, isCreate);
188
+ return recordData;
206
189
  }
207
190
 
208
191
  setRecordId(type: string, id: string, lid: string) {
@@ -213,9 +196,9 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
213
196
  isRecordInUse(type: string, id: string, lid?: string | null): boolean;
214
197
  isRecordInUse(type: string, id: string | null, lid?: string | null): boolean {
215
198
  const resource = constructResource(type, id, lid);
216
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
199
+ const identifier = this.identifierCache.peekRecordIdentifier(resource);
217
200
 
218
- const record = this._store._instanceCache.peek({ identifier, bucket: 'record' });
201
+ const record = identifier && this._store._instanceCache.peek({ identifier, bucket: 'record' });
219
202
 
220
203
  return record ? !(record.isDestroyed || record.isDestroying) : false;
221
204
  }
@@ -224,16 +207,11 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
224
207
  disconnectRecord(type: string, id: string, lid?: string | null): void;
225
208
  disconnectRecord(type: string, id: string | null, lid?: string | null): void {
226
209
  const resource = constructResource(type, id, lid);
227
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
228
- if (HAS_RECORD_DATA_PACKAGE) {
229
- let graph = peekGraph(this);
230
- if (graph) {
231
- graph.remove(identifier);
232
- }
233
- }
234
- let internalModel = internalModelFactoryFor(this._store).peek(identifier);
235
- if (internalModel) {
236
- internalModel.destroyFromRecordData();
210
+ const identifier = this.identifierCache.peekRecordIdentifier(resource);
211
+
212
+ if (identifier) {
213
+ this._store._instanceCache.disconnect(identifier);
214
+ this._pendingNotifies.delete(identifier);
237
215
  }
238
216
  }
239
217
  }
@@ -0,0 +1,96 @@
1
+ import { assert } from '@ember/debug';
2
+ import { DEBUG } from '@glimmer/env';
3
+
4
+ import { LOG_NOTIFICATIONS } from '@ember-data/private-build-infra/debugging';
5
+ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
6
+
7
+ import { isStableIdentifier } from '../caches/identifier-cache';
8
+ import type Store from '../store-service';
9
+ import WeakCache from '../utils/weak-cache';
10
+
11
+ type UnsubscribeToken = object;
12
+
13
+ const Cache = new WeakCache<StableRecordIdentifier, Map<UnsubscribeToken, NotificationCallback>>(
14
+ DEBUG ? 'subscribers' : ''
15
+ );
16
+ Cache._generator = () => new Map();
17
+ const Tokens = new WeakCache<UnsubscribeToken, StableRecordIdentifier>(DEBUG ? 'identifier' : '');
18
+
19
+ export type NotificationType =
20
+ | 'attributes'
21
+ | 'relationships'
22
+ | 'identity'
23
+ | 'errors'
24
+ | 'meta'
25
+ | 'unload'
26
+ | 'state'
27
+ | 'property'; // 'property' is an internal EmberData only transition period concept.
28
+
29
+ export interface NotificationCallback {
30
+ (
31
+ identifier: StableRecordIdentifier,
32
+ notificationType: 'attributes' | 'relationships' | 'property',
33
+ key?: string
34
+ ): void;
35
+ (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): void;
36
+ (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
37
+ }
38
+
39
+ export function unsubscribe(token: UnsubscribeToken) {
40
+ let identifier = Tokens.get(token);
41
+ if (!identifier) {
42
+ throw new Error('Passed unknown unsubscribe token to unsubscribe');
43
+ }
44
+ Tokens.delete(token);
45
+ const map = Cache.get(identifier);
46
+ map?.delete(token);
47
+ }
48
+ /*
49
+ Currently only support a single callback per identifier
50
+ */
51
+ export default class NotificationManager {
52
+ constructor(private store: Store) {}
53
+
54
+ subscribe(identifier: StableRecordIdentifier, callback: NotificationCallback): UnsubscribeToken {
55
+ assert(`Expected to receive a stable Identifier to subscribe to`, isStableIdentifier(identifier));
56
+ let map = Cache.lookup(identifier);
57
+ let unsubToken = {};
58
+ map.set(unsubToken, callback);
59
+ Tokens.set(unsubToken, identifier);
60
+ return unsubToken;
61
+ }
62
+
63
+ unsubscribe(token: UnsubscribeToken) {
64
+ unsubscribe(token);
65
+ }
66
+
67
+ notify(identifier: StableRecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean;
68
+ notify(identifier: StableRecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean;
69
+ notify(identifier: StableRecordIdentifier, value: NotificationType, key?: string): boolean {
70
+ if (!isStableIdentifier(identifier)) {
71
+ if (LOG_NOTIFICATIONS) {
72
+ // eslint-disable-next-line no-console
73
+ console.log(
74
+ `Notifying: Expected to receive a stable Identifier to notify '${value}' '${key}' with, but ${String(
75
+ identifier
76
+ )} is not in the cache`,
77
+ identifier
78
+ );
79
+ }
80
+ return false;
81
+ }
82
+
83
+ if (LOG_NOTIFICATIONS) {
84
+ // eslint-disable-next-line no-console
85
+ console.log(`Notifying: ${String(identifier)}\t${value}\t${key}`);
86
+ }
87
+ let callbackMap = Cache.get(identifier);
88
+ if (!callbackMap || !callbackMap.size) {
89
+ return false;
90
+ }
91
+ callbackMap.forEach((cb) => {
92
+ cb(identifier, value, key);
93
+ });
94
+ return true;
95
+ }
96
+ }
@@ -19,14 +19,14 @@ import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-ser
19
19
  import type { FindOptions } from '@ember-data/types/q/store';
20
20
  import type { Dict } from '@ember-data/types/q/utils';
21
21
 
22
- import coerceId from './coerce-id';
23
- import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './common';
24
- import type Store from './core-store';
25
- import ShimModelClass from './model/shim-model-class';
22
+ import ShimModelClass from '../legacy-model-support/shim-model-class';
23
+ import type Store from '../store-service';
24
+ import coerceId from '../utils/coerce-id';
25
+ import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from '../utils/common';
26
+ import { normalizeResponseHelper } from '../utils/serializer-response';
27
+ import WeakCache from '../utils/weak-cache';
26
28
  import RequestCache from './request-cache';
27
- import { normalizeResponseHelper } from './serializer-response';
28
29
  import Snapshot from './snapshot';
29
- import WeakCache from './weak-cache';
30
30
 
31
31
  function payloadIsNotBlank(adapterPayload): boolean {
32
32
  if (Array.isArray(adapterPayload)) {
@@ -84,6 +84,10 @@ export default class FetchManager {
84
84
  this.isDestroyed = false;
85
85
  }
86
86
 
87
+ clearEntries(identifier: StableRecordIdentifier) {
88
+ delete this.requestCache._done[identifier.lid];
89
+ }
90
+
87
91
  /**
88
92
  This method is called by `record.save`, and gets passed a
89
93
  resolver for the promise that `record.save` returns.
@@ -126,10 +130,10 @@ export default class FetchManager {
126
130
  let adapter = this._store.adapterFor(identifier.type);
127
131
  let operation = options[SaveOp];
128
132
 
129
- let internalModel = snapshot._internalModel;
130
133
  let modelName = snapshot.modelName;
131
134
  let store = this._store;
132
135
  let modelClass = store.modelFor(modelName);
136
+ const record = store._instanceCache.getRecord(identifier);
133
137
 
134
138
  assert(`You tried to update a record but you have no adapter (for ${modelName})`, adapter);
135
139
  assert(
@@ -139,16 +143,16 @@ export default class FetchManager {
139
143
 
140
144
  let promise = resolve().then(() => adapter[operation](store, modelClass, snapshot));
141
145
  let serializer: SerializerWithParseErrors | null = store.serializerFor(modelName);
142
- let label = `DS: Extract and notify about ${operation} completion of ${internalModel}`;
146
+ let label = `DS: Extract and notify about ${operation} completion of ${identifier}`;
143
147
 
144
148
  assert(
145
149
  `Your adapter's '${operation}' method must return a value, but it returned 'undefined'`,
146
150
  promise !== undefined
147
151
  );
148
152
 
149
- promise = _guard(guardDestroyedStore(promise, store, label), _bind(_objectIsAlive, internalModel)).then(
153
+ promise = _guard(guardDestroyedStore(promise, store, label), _bind(_objectIsAlive, record)).then(
150
154
  (adapterPayload) => {
151
- if (!_objectIsAlive(internalModel)) {
155
+ if (!_objectIsAlive(record)) {
152
156
  if (DEPRECATE_RSVP_PROMISE) {
153
157
  deprecate(
154
158
  `A Promise while saving ${modelName} did not resolve by the time your model was destroyed. This will error in a future release.`,
@@ -239,11 +243,8 @@ export default class FetchManager {
239
243
  }
240
244
 
241
245
  let resolverPromise = resolver.promise;
242
-
243
- // TODO replace with some form of record state cache
244
246
  const store = this._store;
245
- const internalModel = store._instanceCache.getInternalModel(identifier);
246
- const isLoading = !internalModel.isLoaded; // we don't use isLoading directly because we are the request
247
+ const isLoading = !store._instanceCache.recordIsLoaded(identifier); // we don't use isLoading directly because we are the request
247
248
 
248
249
  const promise = resolverPromise.then(
249
250
  (payload) => {
@@ -262,8 +263,9 @@ export default class FetchManager {
262
263
  return identifier;
263
264
  },
264
265
  (error) => {
265
- if (internalModel.isEmpty || isLoading) {
266
- internalModel.unloadRecord();
266
+ const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
267
+ if (!recordData || recordData.isEmpty?.() || isLoading) {
268
+ store._instanceCache.unloadRecord(identifier);
267
269
  }
268
270
  throw error;
269
271
  }