@ember-data/store 4.8.0-alpha.3 → 4.8.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.
Files changed (27) hide show
  1. package/addon/-private/caches/identifier-cache.ts +65 -66
  2. package/addon/-private/caches/instance-cache.ts +173 -236
  3. package/addon/-private/caches/record-data-for.ts +1 -6
  4. package/addon/-private/index.ts +13 -10
  5. package/addon/-private/legacy-model-support/record-reference.ts +12 -10
  6. package/addon/-private/legacy-model-support/schema-definition-service.ts +9 -4
  7. package/addon/-private/legacy-model-support/shim-model-class.ts +17 -10
  8. package/addon/-private/managers/record-array-manager.ts +282 -321
  9. package/addon/-private/managers/record-data-manager.ts +822 -0
  10. package/addon/-private/managers/record-data-store-wrapper.ts +295 -91
  11. package/addon/-private/managers/record-notification-manager.ts +45 -32
  12. package/addon/-private/network/fetch-manager.ts +292 -300
  13. package/addon/-private/network/finders.js +11 -6
  14. package/addon/-private/network/request-cache.ts +20 -17
  15. package/addon/-private/network/snapshot-record-array.ts +12 -29
  16. package/addon/-private/network/snapshot.ts +25 -27
  17. package/addon/-private/proxies/promise-proxies.ts +72 -11
  18. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  19. package/addon/-private/store-service.ts +380 -114
  20. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  21. package/addon/-private/utils/promise-record.ts +2 -3
  22. package/addon/-private/utils/uuid-polyfill.ts +71 -0
  23. package/package.json +11 -7
  24. package/addon/-private/backburner.js +0 -25
  25. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -128
  26. package/addon/-private/record-arrays/record-array.ts +0 -320
  27. package/addon/-private/utils/weak-cache.ts +0 -125
@@ -1,28 +1,29 @@
1
- import type { RelationshipDefinition } from '@ember-data/model/-private/relationship-meta';
2
- import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
1
+ import { assert, deprecate } from '@ember/debug';
2
+
3
+ import { DEPRECATE_V1CACHE_STORE_APIS } from '@ember-data/private-build-infra/deprecations';
4
+ import type { RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
3
5
  import type { RecordData } from '@ember-data/types/q/record-data';
6
+ import type { AttributesSchema, RelationshipsSchema } from '@ember-data/types/q/record-data-schemas';
4
7
  import type {
5
- AttributesSchema,
6
- RelationshipSchema,
7
- RelationshipsSchema,
8
- } from '@ember-data/types/q/record-data-schemas';
9
- import type { RecordDataStoreWrapper as StoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
8
+ LegacyRecordDataStoreWrapper,
9
+ V2RecordDataStoreWrapper as StoreWrapper,
10
+ } from '@ember-data/types/q/record-data-store-wrapper';
11
+ import { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service';
10
12
 
11
- import type { IdentifierCache } from '../caches/identifier-cache';
13
+ import { IdentifierCache, isStableIdentifier } from '../caches/identifier-cache';
12
14
  import type Store from '../store-service';
15
+ import coerceId from '../utils/coerce-id';
13
16
  import constructResource from '../utils/construct-resource';
17
+ import normalizeModelName from '../utils/normalize-model-name';
18
+ import { NotificationType } from './record-notification-manager';
14
19
 
15
20
  /**
16
21
  @module @ember-data/store
17
22
  */
18
23
 
19
- function metaIsRelationshipDefinition(meta: RelationshipSchema): meta is RelationshipDefinition {
20
- return typeof (meta as RelationshipDefinition)._inverseKey === 'function';
21
- }
22
-
23
- export default class RecordDataStoreWrapper implements StoreWrapper {
24
+ class LegacyWrapper implements LegacyRecordDataStoreWrapper {
24
25
  declare _willNotify: boolean;
25
- declare _pendingNotifies: Map<StableRecordIdentifier, Map<string, string>>;
26
+ declare _pendingNotifies: Map<StableRecordIdentifier, Set<string>>;
26
27
  declare _store: Store;
27
28
 
28
29
  constructor(_store: Store) {
@@ -35,32 +36,27 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
35
36
  return this._store.identifierCache;
36
37
  }
37
38
 
38
- _scheduleNotification(identifier: StableRecordIdentifier, key: string, kind: 'belongsTo' | 'hasMany') {
39
+ _scheduleNotification(identifier: StableRecordIdentifier, key: string) {
39
40
  let pending = this._pendingNotifies.get(identifier);
40
41
 
41
42
  if (!pending) {
42
- pending = new Map();
43
+ pending = new Set();
43
44
  this._pendingNotifies.set(identifier, pending);
44
45
  }
45
- pending.set(key, kind);
46
+ pending.add(key);
46
47
 
47
48
  if (this._willNotify === true) {
48
49
  return;
49
50
  }
50
51
 
51
52
  this._willNotify = true;
52
- let backburner: any = this._store._backburner;
53
-
54
- backburner.schedule('notify', this, this._flushNotifications);
55
- }
56
-
57
- notifyErrorsChange(type: string, id: string, lid: string | null): void;
58
- notifyErrorsChange(type: string, id: string | null, lid: string): void;
59
- notifyErrorsChange(type: string, id: string | null, lid: string | null): void {
60
- const resource = constructResource(type, id, lid);
61
- const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
62
-
63
- this._store._notificationManager.notify(identifier, 'errors');
53
+ // it's possible a RecordData adhoc notifies us,
54
+ // in which case we sync flush
55
+ if (this._store._cbs) {
56
+ this._store._schedule('notify', () => this._flushNotifications());
57
+ } else {
58
+ this._flushNotifications();
59
+ }
64
60
  }
65
61
 
66
62
  _flushNotifications(): void {
@@ -72,61 +68,93 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
72
68
  this._pendingNotifies = new Map();
73
69
  this._willNotify = false;
74
70
 
75
- pending.forEach((map, identifier) => {
76
- map.forEach((kind, key) => {
71
+ pending.forEach((set, identifier) => {
72
+ set.forEach((key) => {
77
73
  this._store._notificationManager.notify(identifier, 'relationships', key);
78
74
  });
79
75
  });
80
76
  }
81
77
 
82
- attributesDefinitionFor(type: string): AttributesSchema {
83
- return this._store.getSchemaDefinitionService().attributesDefinitionFor({ type });
84
- }
85
-
86
- relationshipsDefinitionFor(type: string): RelationshipsSchema {
87
- return this._store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
88
- }
78
+ notifyChange(identifier: StableRecordIdentifier, namespace: NotificationType, key?: string): void {
79
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
89
80
 
90
- inverseForRelationship(type: string, key: string): string | null {
91
- const modelClass = this._store.modelFor(type);
92
- const definition = this.relationshipsDefinitionFor(type)[key];
93
- if (!definition) {
94
- return null;
81
+ // TODO do we still get value from this?
82
+ if (namespace === 'relationships' && key) {
83
+ this._scheduleNotification(identifier, key);
84
+ return;
95
85
  }
96
86
 
97
- if (metaIsRelationshipDefinition(definition)) {
98
- return definition._inverseKey(this._store, modelClass);
99
- } else if (definition.options && definition.options.inverse !== undefined) {
100
- return definition.options.inverse;
101
- } else {
102
- return null;
87
+ this._store._notificationManager.notify(identifier, namespace, key);
88
+
89
+ if (namespace === 'state') {
90
+ this._store.recordArrayManager.identifierChanged(identifier);
103
91
  }
104
92
  }
105
93
 
106
- inverseIsAsyncForRelationship(type: string, key: string): boolean {
107
- const modelClass = this._store.modelFor(type);
108
- const definition = this.relationshipsDefinitionFor(type)[key];
109
- if (!definition) {
110
- return false;
94
+ notifyErrorsChange(type: string, id: string, lid: string | null): void;
95
+ notifyErrorsChange(type: string, id: string | null, lid: string): void;
96
+ notifyErrorsChange(type: string, id: string | null, lid: string | null): void {
97
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
98
+ deprecate(`StoreWrapper.notifyErrorsChange has been deprecated in favor of StoreWrapper.notifyChange`, false, {
99
+ id: 'ember-data:deprecate-v1cache-store-apis',
100
+ for: 'ember-data',
101
+ until: '5.0',
102
+ since: { enabled: '4.8', available: '4.8' },
103
+ });
111
104
  }
105
+ const resource = constructResource(type, id, lid);
106
+ const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
112
107
 
113
- if (definition.options && definition.options.inverse === null) {
114
- return false;
108
+ this._store._notificationManager.notify(identifier, 'errors');
109
+ }
110
+
111
+ attributesDefinitionFor(type: string): AttributesSchema {
112
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
113
+ deprecate(
114
+ `StoreWrapper.attributesDefinitionFor has been deprecated in favor of StoreWrapper.getSchemaDefinitionService().attributesDefinitionFor`,
115
+ false,
116
+ {
117
+ id: 'ember-data:deprecate-v1cache-store-apis',
118
+ for: 'ember-data',
119
+ until: '5.0',
120
+ since: { enabled: '4.8', available: '4.8' },
121
+ }
122
+ );
115
123
  }
116
- if ((definition as unknown as { inverseIsAsync?: boolean }).inverseIsAsync !== undefined) {
117
- // TODO do we need to amend the RFC for this prop?
118
- // else we should add it to the TS interface and document.
119
- return !!(definition as unknown as { inverseIsAsync: boolean }).inverseIsAsync;
120
- } else if (metaIsRelationshipDefinition(definition)) {
121
- return definition._inverseIsAsync(this._store, modelClass);
122
- } else {
123
- return false;
124
+ return this._store.getSchemaDefinitionService().attributesDefinitionFor({ type });
125
+ }
126
+
127
+ relationshipsDefinitionFor(type: string): RelationshipsSchema {
128
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
129
+ deprecate(
130
+ `StoreWrapper.relationshipsDefinitionFor has been deprecated in favor of StoreWrapper.getSchemaDefinitionService().relationshipsDefinitionFor`,
131
+ false,
132
+ {
133
+ id: 'ember-data:deprecate-v1cache-store-apis',
134
+ for: 'ember-data',
135
+ until: '5.0',
136
+ since: { enabled: '4.8', available: '4.8' },
137
+ }
138
+ );
124
139
  }
140
+ return this._store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
141
+ }
142
+
143
+ getSchemaDefinitionService(): SchemaDefinitionService {
144
+ return this._store.getSchemaDefinitionService();
125
145
  }
126
146
 
127
147
  notifyPropertyChange(type: string, id: string | null, lid: string, key?: string): void;
128
148
  notifyPropertyChange(type: string, id: string, lid: string | null | undefined, key?: string): void;
129
149
  notifyPropertyChange(type: string, id: string | null, lid: string | null | undefined, key?: string): void {
150
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
151
+ deprecate(`StoreWrapper.notifyPropertyChange has been deprecated in favor of StoreWrapper.notifyChange`, false, {
152
+ id: 'ember-data:deprecate-v1cache-store-apis',
153
+ for: 'ember-data',
154
+ until: '5.0',
155
+ since: { enabled: '4.8', available: '4.8' },
156
+ });
157
+ }
130
158
  const resource = constructResource(type, id, lid);
131
159
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
132
160
 
@@ -136,65 +164,122 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
136
164
  notifyHasManyChange(type: string, id: string | null, lid: string, key: string): void;
137
165
  notifyHasManyChange(type: string, id: string, lid: string | null | undefined, key: string): void;
138
166
  notifyHasManyChange(type: string, id: string | null, lid: string | null | undefined, key: string): void {
167
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
168
+ deprecate(`StoreWrapper.notifyHasManyChange has been deprecated in favor of StoreWrapper.notifyChange`, false, {
169
+ id: 'ember-data:deprecate-v1cache-store-apis',
170
+ for: 'ember-data',
171
+ until: '5.0',
172
+ since: { enabled: '4.8', available: '4.8' },
173
+ });
174
+ }
139
175
  const resource = constructResource(type, id, lid);
140
176
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
141
- this._scheduleNotification(identifier, key, 'hasMany');
177
+ this._scheduleNotification(identifier, key);
142
178
  }
143
179
 
144
180
  notifyBelongsToChange(type: string, id: string | null, lid: string, key: string): void;
145
181
  notifyBelongsToChange(type: string, id: string, lid: string | null | undefined, key: string): void;
146
182
  notifyBelongsToChange(type: string, id: string | null, lid: string | null | undefined, key: string): void {
183
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
184
+ deprecate(`StoreWrapper.notifyBelongsToChange has been deprecated in favor of StoreWrapper.notifyChange`, false, {
185
+ id: 'ember-data:deprecate-v1cache-store-apis',
186
+ for: 'ember-data',
187
+ until: '5.0',
188
+ since: { enabled: '4.8', available: '4.8' },
189
+ });
190
+ }
147
191
  const resource = constructResource(type, id, lid);
148
192
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
149
193
 
150
- this._scheduleNotification(identifier, key, 'belongsTo');
194
+ this._scheduleNotification(identifier, key);
151
195
  }
152
196
 
153
197
  notifyStateChange(type: string, id: string, lid: string | null, key?: string): void;
154
198
  notifyStateChange(type: string, id: string | null, lid: string, key?: string): void;
155
199
  notifyStateChange(type: string, id: string | null, lid: string | null, key?: string): void {
200
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
201
+ deprecate(`StoreWrapper.notifyStateChange has been deprecated in favor of StoreWrapper.notifyChange`, false, {
202
+ id: 'ember-data:deprecate-v1cache-store-apis',
203
+ for: 'ember-data',
204
+ until: '5.0',
205
+ since: { enabled: '4.8', available: '4.8' },
206
+ });
207
+ }
156
208
  const resource = constructResource(type, id, lid);
157
209
  const identifier = this.identifierCache.getOrCreateRecordIdentifier(resource);
158
210
 
159
211
  this._store._notificationManager.notify(identifier, 'state');
160
-
161
- if (!key || key === 'isDeletionCommitted') {
162
- this._store.recordArrayManager.recordDidChange(identifier);
163
- }
212
+ this._store.recordArrayManager.identifierChanged(identifier);
164
213
  }
165
214
 
166
215
  recordDataFor(type: string, id: string, lid?: string | null): RecordData;
167
216
  recordDataFor(type: string, id: string | null, lid: string): RecordData;
168
217
  recordDataFor(type: string): RecordData;
169
- recordDataFor(type: string, id?: string | null, lid?: string | null): RecordData {
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));
218
+ recordDataFor(type: StableRecordIdentifier): RecordData;
219
+ recordDataFor(type: string | StableRecordIdentifier, id?: string | null, lid?: string | null): RecordData {
220
+ let identifier: StableRecordIdentifier;
221
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
222
+ if (!isStableIdentifier(type)) {
223
+ // we also deprecate create capability. This behavior was problematic because
224
+ // there's no outside association between this RecordData and an Identifier.
225
+ // It's likely a mistake when we hit this codepath, but we said in an early
226
+ // RFC we'd allow this.
227
+ // With V2 we are enforcing someone to use the record-data and identifier-cache APIs to
228
+ // create a new identifier and then call clientDidCreate on the RecordData
229
+ // instead.
230
+ identifier =
231
+ !id && !lid
232
+ ? this.identifierCache.createIdentifierForNewRecord({ type: type })
233
+ : this.identifierCache.getOrCreateRecordIdentifier(constructResource(type, id, lid));
234
+ } else {
235
+ identifier = type;
236
+ }
237
+ } else {
238
+ assert(`Expected a stable identifier`, isStableIdentifier(type));
239
+ identifier = type;
240
+ }
180
241
 
181
242
  const recordData = this._store._instanceCache.getRecordData(identifier);
182
243
 
183
244
  if (!id && !lid) {
184
- recordData.clientDidCreate();
185
- this._store.recordArrayManager.recordDidChange(identifier);
245
+ recordData.clientDidCreate(identifier);
246
+ this._store.recordArrayManager.identifierAdded(identifier);
186
247
  }
187
248
 
188
249
  return recordData;
189
250
  }
190
251
 
191
- setRecordId(type: string, id: string, lid: string) {
192
- this._store._instanceCache.setRecordId(type, id, lid);
252
+ setRecordId(type: string | StableRecordIdentifier, id: string, lid?: string) {
253
+ let identifier: StableRecordIdentifier | undefined;
254
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
255
+ if (!isStableIdentifier(type)) {
256
+ const modelName = normalizeModelName(type);
257
+ const resource = constructResource(modelName, null, coerceId(lid));
258
+ identifier = this.identifierCache.peekRecordIdentifier(resource);
259
+ } else {
260
+ identifier = type;
261
+ }
262
+ } else {
263
+ assert(`Expected a stable identifier`, isStableIdentifier(type));
264
+ identifier = type;
265
+ }
266
+
267
+ assert(`Unable to find an identifier to update the ID for for ${lid}`, identifier);
268
+
269
+ this._store._instanceCache.setRecordId(identifier, id);
193
270
  }
194
271
 
195
272
  isRecordInUse(type: string, id: string | null, lid: string): boolean;
196
273
  isRecordInUse(type: string, id: string, lid?: string | null): boolean;
197
274
  isRecordInUse(type: string, id: string | null, lid?: string | null): boolean {
275
+ if (DEPRECATE_V1CACHE_STORE_APIS) {
276
+ deprecate(`StoreWrapper.isRecordInUSe has been deprecated in favor of StoreWrapper.hasRecord`, false, {
277
+ id: 'ember-data:deprecate-v1cache-store-apis',
278
+ for: 'ember-data',
279
+ until: '5.0',
280
+ since: { enabled: '4.8', available: '4.8' },
281
+ });
282
+ }
198
283
  const resource = constructResource(type, id, lid);
199
284
  const identifier = this.identifierCache.peekRecordIdentifier(resource);
200
285
 
@@ -203,15 +288,134 @@ export default class RecordDataStoreWrapper implements StoreWrapper {
203
288
  return record ? !(record.isDestroyed || record.isDestroying) : false;
204
289
  }
205
290
 
291
+ hasRecord(identifier: StableRecordIdentifier): boolean {
292
+ return Boolean(this._store._instanceCache.peek({ identifier, bucket: 'record' }));
293
+ }
294
+
206
295
  disconnectRecord(type: string, id: string | null, lid: string): void;
207
296
  disconnectRecord(type: string, id: string, lid?: string | null): void;
208
- disconnectRecord(type: string, id: string | null, lid?: string | null): void {
209
- const resource = constructResource(type, id, lid);
210
- const identifier = this.identifierCache.peekRecordIdentifier(resource);
297
+ disconnectRecord(type: StableRecordIdentifier): void;
298
+ disconnectRecord(type: string | StableRecordIdentifier, id?: string | null, lid?: string | null): void {
299
+ let identifier: StableRecordIdentifier;
300
+ if (DEPRECATE_V1CACHE_STORE_APIS && typeof type === 'string') {
301
+ deprecate(
302
+ `StoreWrapper.disconnectRecord(<type>) has been deprecated in favor of StoreWrapper.disconnectRecord(<identifier>)`,
303
+ false,
304
+ {
305
+ id: 'ember-data:deprecate-v1cache-store-apis',
306
+ for: 'ember-data',
307
+ until: '5.0',
308
+ since: { enabled: '4.8', available: '4.8' },
309
+ }
310
+ );
311
+ let resource = constructResource(type, id, lid) as RecordIdentifier;
312
+ identifier = this.identifierCache.peekRecordIdentifier(resource)!;
313
+ } else {
314
+ identifier = type as StableRecordIdentifier;
315
+ }
316
+
317
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
318
+
319
+ this._store._instanceCache.disconnect(identifier);
320
+ this._pendingNotifies.delete(identifier);
321
+ }
322
+ }
323
+
324
+ class V2RecordDataStoreWrapper implements StoreWrapper {
325
+ declare _willNotify: boolean;
326
+ declare _pendingNotifies: Map<StableRecordIdentifier, Set<string>>;
327
+ declare _store: Store;
211
328
 
212
- if (identifier) {
213
- this._store._instanceCache.disconnect(identifier);
214
- this._pendingNotifies.delete(identifier);
329
+ constructor(_store: Store) {
330
+ this._store = _store;
331
+ this._willNotify = false;
332
+ this._pendingNotifies = new Map();
333
+ }
334
+
335
+ get identifierCache(): IdentifierCache {
336
+ return this._store.identifierCache;
337
+ }
338
+
339
+ _scheduleNotification(identifier: StableRecordIdentifier, key: string) {
340
+ let pending = this._pendingNotifies.get(identifier);
341
+
342
+ if (!pending) {
343
+ pending = new Set();
344
+ this._pendingNotifies.set(identifier, pending);
345
+ }
346
+ pending.add(key);
347
+
348
+ if (this._willNotify === true) {
349
+ return;
350
+ }
351
+
352
+ this._willNotify = true;
353
+ // it's possible a RecordData adhoc notifies us,
354
+ // in which case we sync flush
355
+ if (this._store._cbs) {
356
+ this._store._schedule('notify', () => this._flushNotifications());
357
+ } else {
358
+ this._flushNotifications();
215
359
  }
216
360
  }
361
+
362
+ _flushNotifications(): void {
363
+ if (this._willNotify === false) {
364
+ return;
365
+ }
366
+
367
+ let pending = this._pendingNotifies;
368
+ this._pendingNotifies = new Map();
369
+ this._willNotify = false;
370
+
371
+ pending.forEach((set, identifier) => {
372
+ set.forEach((key) => {
373
+ this._store._notificationManager.notify(identifier, 'relationships', key);
374
+ });
375
+ });
376
+ }
377
+
378
+ notifyChange(identifier: StableRecordIdentifier, namespace: NotificationType, key?: string): void {
379
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
380
+
381
+ // TODO do we still get value from this?
382
+ if (namespace === 'relationships' && key) {
383
+ this._scheduleNotification(identifier, key);
384
+ return;
385
+ }
386
+
387
+ this._store._notificationManager.notify(identifier, namespace, key);
388
+
389
+ if (namespace === 'state') {
390
+ this._store.recordArrayManager.identifierChanged(identifier);
391
+ }
392
+ }
393
+
394
+ getSchemaDefinitionService(): SchemaDefinitionService {
395
+ return this._store.getSchemaDefinitionService();
396
+ }
397
+
398
+ recordDataFor(identifier: StableRecordIdentifier): RecordData {
399
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
400
+
401
+ return this._store._instanceCache.getRecordData(identifier);
402
+ }
403
+
404
+ setRecordId(identifier: StableRecordIdentifier, id: string) {
405
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
406
+ this._store._instanceCache.setRecordId(identifier, id);
407
+ }
408
+
409
+ hasRecord(identifier: StableRecordIdentifier): boolean {
410
+ return Boolean(this._store._instanceCache.peek({ identifier, bucket: 'record' }));
411
+ }
412
+
413
+ disconnectRecord(identifier: StableRecordIdentifier): void {
414
+ assert(`Expected a stable identifier`, isStableIdentifier(identifier));
415
+ this._store._instanceCache.disconnect(identifier);
416
+ this._pendingNotifies.delete(identifier);
417
+ }
217
418
  }
419
+ export type RecordDataStoreWrapper = LegacyWrapper | V2RecordDataStoreWrapper;
420
+
421
+ export const RecordDataStoreWrapper = DEPRECATE_V1CACHE_STORE_APIS ? LegacyWrapper : V2RecordDataStoreWrapper;
@@ -6,67 +6,74 @@ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
6
6
 
7
7
  import { isStableIdentifier } from '../caches/identifier-cache';
8
8
  import type Store from '../store-service';
9
- import WeakCache from '../utils/weak-cache';
10
9
 
11
10
  type UnsubscribeToken = object;
11
+ let tokenId = 0;
12
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' : '');
13
+ const Cache = new Map<StableRecordIdentifier, Map<UnsubscribeToken, NotificationCallback>>();
14
+ const Tokens = new Map<UnsubscribeToken, StableRecordIdentifier>();
18
15
 
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.
16
+ export type NotificationType = 'attributes' | 'relationships' | 'identity' | 'errors' | 'meta' | 'state';
28
17
 
29
18
  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;
19
+ (identifier: StableRecordIdentifier, notificationType: 'attributes' | 'relationships', key?: string): void;
20
+ (identifier: StableRecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'state'): void;
36
21
  (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void;
37
22
  }
38
23
 
24
+ // TODO this isn't importable anyway, remove and use a map on the manager?
39
25
  export function unsubscribe(token: UnsubscribeToken) {
40
26
  let identifier = Tokens.get(token);
41
- if (!identifier) {
42
- throw new Error('Passed unknown unsubscribe token to unsubscribe');
27
+ if (LOG_NOTIFICATIONS && !identifier) {
28
+ // eslint-disable-next-line no-console
29
+ console.log('Passed unknown unsubscribe token to unsubscribe', identifier);
30
+ }
31
+ if (identifier) {
32
+ Tokens.delete(token);
33
+ const map = Cache.get(identifier);
34
+ map?.delete(token);
43
35
  }
44
- Tokens.delete(token);
45
- const map = Cache.get(identifier);
46
- map?.delete(token);
47
36
  }
48
37
  /*
49
38
  Currently only support a single callback per identifier
50
39
  */
51
40
  export default class NotificationManager {
52
- constructor(private store: Store) {}
41
+ declare store: Store;
42
+ declare isDestroyed: boolean;
43
+ constructor(store: Store) {
44
+ this.store = store;
45
+ this.isDestroyed = false;
46
+ }
53
47
 
54
48
  subscribe(identifier: StableRecordIdentifier, callback: NotificationCallback): UnsubscribeToken {
55
49
  assert(`Expected to receive a stable Identifier to subscribe to`, isStableIdentifier(identifier));
56
- let map = Cache.lookup(identifier);
57
- let unsubToken = {};
50
+ let map = Cache.get(identifier);
51
+
52
+ if (!map) {
53
+ map = new Map();
54
+ Cache.set(identifier, map);
55
+ }
56
+
57
+ let unsubToken = DEBUG ? { _tokenRef: tokenId++ } : {};
58
58
  map.set(unsubToken, callback);
59
59
  Tokens.set(unsubToken, identifier);
60
60
  return unsubToken;
61
61
  }
62
62
 
63
63
  unsubscribe(token: UnsubscribeToken) {
64
- unsubscribe(token);
64
+ if (!this.isDestroyed) {
65
+ unsubscribe(token);
66
+ }
65
67
  }
66
68
 
67
- notify(identifier: StableRecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean;
68
- notify(identifier: StableRecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean;
69
+ // deactivated type signature overloads because pass-through was failing to match any. Bring back if possible.
70
+ // notify(identifier: StableRecordIdentifier, value: 'attributes' | 'relationships', key?: string): boolean;
71
+ // notify(identifier: StableRecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'state'): boolean;
69
72
  notify(identifier: StableRecordIdentifier, value: NotificationType, key?: string): boolean {
73
+ assert(
74
+ `Notify does not accept a key argument for the namespace '${value}'. Received key '${key}'.`,
75
+ !key || value === 'attributes' || value === 'relationships'
76
+ );
70
77
  if (!isStableIdentifier(identifier)) {
71
78
  if (LOG_NOTIFICATIONS) {
72
79
  // eslint-disable-next-line no-console
@@ -93,4 +100,10 @@ export default class NotificationManager {
93
100
  });
94
101
  return true;
95
102
  }
103
+
104
+ destroy() {
105
+ this.isDestroyed = true;
106
+ Tokens.clear();
107
+ Cache.clear();
108
+ }
96
109
  }