@ember-data/store 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/addon/-debug/index.js +35 -13
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
  3. package/addon/-private/caches/instance-cache.ts +690 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
  5. package/addon/-private/index.ts +44 -24
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
  9. package/addon/-private/managers/record-array-manager.ts +377 -0
  10. package/addon/-private/managers/record-data-manager.ts +845 -0
  11. package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
  12. package/addon/-private/managers/record-notification-manager.ts +109 -0
  13. package/addon/-private/network/fetch-manager.ts +567 -0
  14. package/addon/-private/{finders.js → network/finders.js} +14 -17
  15. package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
  16. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
  17. package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
  18. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
  19. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  20. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  21. package/addon/-private/{core-store.ts → store-service.ts} +574 -215
  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/utils/is-non-empty-string.ts +1 -1
  27. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
  28. package/addon/-private/utils/promise-record.ts +5 -6
  29. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  30. package/addon/-private/utils/uuid-polyfill.ts +73 -0
  31. package/package.json +12 -8
  32. package/addon/-private/backburner.js +0 -25
  33. package/addon/-private/errors-utils.js +0 -146
  34. package/addon/-private/fetch-manager.ts +0 -597
  35. package/addon/-private/identity-map.ts +0 -54
  36. package/addon/-private/instance-cache.ts +0 -387
  37. package/addon/-private/internal-model-factory.ts +0 -359
  38. package/addon/-private/internal-model-map.ts +0 -121
  39. package/addon/-private/model/internal-model.ts +0 -602
  40. package/addon/-private/record-array-manager.ts +0 -444
  41. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
  42. package/addon/-private/record-arrays/record-array.ts +0 -318
  43. package/addon/-private/record-data-store-wrapper.ts +0 -243
  44. package/addon/-private/record-notification-manager.ts +0 -73
  45. package/addon/-private/weak-cache.ts +0 -125
@@ -0,0 +1,845 @@
1
+ import { assert, deprecate } from '@ember/debug';
2
+
3
+ import type { LocalRelationshipOperation } from '@ember-data/record-data/-private/graph/-operations';
4
+ import type {
5
+ CollectionResourceRelationship,
6
+ SingleResourceRelationship,
7
+ } from '@ember-data/types/q/ember-data-json-api';
8
+ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
9
+ import type { ChangedAttributesHash, MergeOperation, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
10
+ import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
11
+ import type { Dict } from '@ember-data/types/q/utils';
12
+
13
+ import { isStableIdentifier } from '../caches/identifier-cache';
14
+ import type Store from '../store-service';
15
+
16
+ /**
17
+ * The RecordDataManager wraps a RecordData cache
18
+ * enforcing that only the public API surface area
19
+ * is exposed.
20
+ *
21
+ * This class is the the return value of both the
22
+ * `recordDataFor` function supplied to the store
23
+ * hook `instantiateRecord`, and the `recordDataFor`
24
+ * method on the `RecordDataStoreWrapper`. It is not
25
+ * directly instantiatable.
26
+ *
27
+ * It handles translating between cache versions when
28
+ * necessary, for instance when a Store is configured
29
+ * to use both a v1 and a v2 cache depending on some
30
+ * heuristic.
31
+ *
32
+ * Starting with the v2 spec, the cache is designed such
33
+ * that it may be implemented as a singleton. However,
34
+ * because the v1 spec was not designed for this whenever
35
+ * we encounter any v1 cache we must wrap all caches, even
36
+ * singletons, in non-singleton managers to preserve v1
37
+ * compatibility.
38
+ *
39
+ * To avoid this performance penalty being paid by all
40
+ * applications, singleton behavior may be opted-in via
41
+ * the configuration supplied to your Ember application
42
+ * at build time. This effectively removes support for
43
+ * v1 caches.
44
+ *
45
+ * ```js
46
+ * let app = new EmberApp(defaults, {
47
+ * emberData: {
48
+ * useSingletonManager: true
49
+ * },
50
+ * });
51
+ * ```
52
+ *
53
+ * @class RecordDataManager
54
+ * @public
55
+ */
56
+ export class NonSingletonRecordDataManager implements RecordData {
57
+ version: '2' = '2';
58
+
59
+ #store: Store;
60
+ #recordData: RecordData | RecordDataV1;
61
+ #identifier: StableRecordIdentifier;
62
+
63
+ get managedVersion() {
64
+ return this.#recordData.version || '1';
65
+ }
66
+
67
+ constructor(store: Store, recordData: RecordData | RecordDataV1, identifier: StableRecordIdentifier) {
68
+ this.#store = store;
69
+ this.#recordData = recordData;
70
+ this.#identifier = identifier;
71
+
72
+ if (this.#isDeprecated(recordData)) {
73
+ deprecate(
74
+ `This RecordData uses the deprecated V1 RecordData Spec. Upgrade to V2 to maintain compatibility.`,
75
+ false,
76
+ {
77
+ id: 'ember-data:deprecate-v1-cache',
78
+ until: '5.0',
79
+ since: { available: '4.8', enabled: '4.8' },
80
+ for: 'ember-data',
81
+ }
82
+ );
83
+ }
84
+ }
85
+
86
+ #isDeprecated(recordData: RecordData | RecordDataV1): recordData is RecordDataV1 {
87
+ let version = recordData.version || '1';
88
+ return version !== this.version;
89
+ }
90
+
91
+ // Cache
92
+ // =====
93
+
94
+ /**
95
+ * Retrieve the identifier for this v1 recordData
96
+ *
97
+ * DEPRECATED Caches should not be assumed to be 1:1 with resources
98
+ *
99
+ * @method getResourceIdentifier
100
+ * @public
101
+ * @deprecated
102
+ */
103
+ getResourceIdentifier(): StableRecordIdentifier {
104
+ return this.#identifier;
105
+ }
106
+
107
+ /**
108
+ * Push resource data from a remote source into the cache for this identifier
109
+ *
110
+ * @method pushData
111
+ * @public
112
+ * @param identifier
113
+ * @param data
114
+ * @param hasRecord
115
+ * @returns {void | string[]} if `hasRecord` is true then calculated key changes should be returned
116
+ */
117
+ pushData(identifier: StableRecordIdentifier, data: JsonApiResource, hasRecord?: boolean): void | string[] {
118
+ const recordData = this.#recordData;
119
+ // called by something V1
120
+ if (!isStableIdentifier(identifier)) {
121
+ data = identifier as JsonApiResource;
122
+ hasRecord = data as boolean;
123
+ identifier = this.#identifier;
124
+ }
125
+ if (this.#isDeprecated(recordData)) {
126
+ return recordData.pushData(data, hasRecord);
127
+ }
128
+ return recordData.pushData(identifier, data, hasRecord);
129
+ }
130
+
131
+ /**
132
+ * Perform an operation on the cache to update the remote state.
133
+ *
134
+ * Note: currently the only valid operation is a MergeOperation
135
+ * which occurs when a collision of identifiers is detected.
136
+ *
137
+ * @method sync
138
+ * @public
139
+ * @param op the operation to perform
140
+ * @returns {void}
141
+ */
142
+ sync(op: MergeOperation): void {
143
+ const recordData = this.#recordData;
144
+ if (this.#isDeprecated(recordData)) {
145
+ return;
146
+ }
147
+ recordData.sync(op);
148
+ }
149
+
150
+ /**
151
+ * Update resource data with a local mutation. Currently supports operations
152
+ * on relationships only.
153
+ *
154
+ * @method update
155
+ * @public
156
+ * @param operation
157
+ */
158
+ // isCollection is only needed for interop with v1 cache
159
+ update(operation: LocalRelationshipOperation, isResource?: boolean): void {
160
+ if (this.#isDeprecated(this.#recordData)) {
161
+ const cache = this.#store._instanceCache;
162
+ switch (operation.op) {
163
+ case 'addToRelatedRecords':
164
+ this.#recordData.addToHasMany(
165
+ operation.field,
166
+ (operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i)),
167
+ operation.index
168
+ );
169
+ return;
170
+ case 'removeFromRelatedRecords':
171
+ this.#recordData.removeFromHasMany(
172
+ operation.field,
173
+ (operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i))
174
+ );
175
+ return;
176
+ case 'replaceRelatedRecords':
177
+ this.#recordData.setDirtyHasMany(
178
+ operation.field,
179
+ operation.value.map((i) => cache.getRecordData(i))
180
+ );
181
+ return;
182
+ case 'replaceRelatedRecord':
183
+ if (isResource) {
184
+ this.#recordData.setDirtyBelongsTo(
185
+ operation.field,
186
+ operation.value ? cache.getRecordData(operation.value) : null
187
+ );
188
+ return;
189
+ }
190
+ this.#recordData.removeFromHasMany(operation.field, [cache.getRecordData(operation.prior!)]);
191
+ this.#recordData.addToHasMany(operation.field, [cache.getRecordData(operation.value!)], operation.index);
192
+ return;
193
+ case 'sortRelatedRecords':
194
+ return;
195
+ default:
196
+ return;
197
+ }
198
+ } else {
199
+ this.#recordData.update(operation);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * [LIFECYLCE] Signal to the cache that a new record has been instantiated on the client
205
+ *
206
+ * It returns properties from options that should be set on the record during the create
207
+ * process. This return value behavior is deprecated.
208
+ *
209
+ * @method clientDidCreate
210
+ * @public
211
+ * @param identifier
212
+ * @param options
213
+ */
214
+ clientDidCreate(identifier: StableRecordIdentifier, options?: Dict<unknown>): Dict<unknown> {
215
+ // called by something V1
216
+ if (!isStableIdentifier(identifier)) {
217
+ options = identifier;
218
+ identifier = this.#identifier;
219
+ }
220
+ let recordData = this.#recordData;
221
+
222
+ // TODO deprecate return value
223
+ if (this.#isDeprecated(recordData)) {
224
+ recordData.clientDidCreate();
225
+ // if a V2 is calling a V1 we need to call both methods
226
+ return recordData._initRecordCreateOptions(options);
227
+ } else {
228
+ return recordData.clientDidCreate(identifier, options);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Pass options to the cache that were supplied to a new record
234
+ * instantiated on the client.
235
+ *
236
+ * DEPRECATED: options are now passed via `clientDidCreate`
237
+ *
238
+ * @method clientDidCreate
239
+ * @public
240
+ * @deprecated
241
+ * @param options
242
+ */
243
+ _initRecordCreateOptions(options?: Dict<unknown>) {
244
+ let recordData = this.#recordData;
245
+
246
+ if (this.#isDeprecated(recordData)) {
247
+ return recordData._initRecordCreateOptions(options);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * [LIFECYCLE] Signals to the cache that a resource
253
+ * will be part of a save transaction.
254
+ *
255
+ * @method willCommit
256
+ * @public
257
+ * @param identifier
258
+ */
259
+ willCommit(identifier: StableRecordIdentifier): void {
260
+ this.#recordData.willCommit(identifier || this.#identifier);
261
+ }
262
+
263
+ /**
264
+ * [LIFECYCLE] Signals to the cache that a resource
265
+ * was successfully updated as part of a save transaction.
266
+ *
267
+ * @method didCommit
268
+ * @public
269
+ * @param identifier
270
+ * @param data
271
+ */
272
+ didCommit(identifier: StableRecordIdentifier, data: JsonApiResource | null): void {
273
+ // called by something V1
274
+ if (!isStableIdentifier(identifier)) {
275
+ data = identifier;
276
+ identifier = this.#identifier;
277
+ }
278
+ let recordData = this.#recordData;
279
+ this.#isDeprecated(recordData) ? recordData.didCommit(data) : recordData.didCommit(identifier, data);
280
+ }
281
+
282
+ /**
283
+ * [LIFECYCLE] Signals to the cache that a resource
284
+ * was update via a save transaction failed.
285
+ *
286
+ * @method commitWasRejected
287
+ * @public
288
+ * @param identifier
289
+ * @param errors
290
+ */
291
+ commitWasRejected(identifier: StableRecordIdentifier, errors?: JsonApiValidationError[]) {
292
+ this.#recordData.commitWasRejected(identifier || this.#identifier, errors);
293
+ }
294
+
295
+ /**
296
+ * [LIFECYCLE] Signals to the cache that all data for a resource
297
+ * should be cleared.
298
+ *
299
+ * @method unloadRecord
300
+ * @public
301
+ * @param identifier
302
+ */
303
+ unloadRecord(identifier: StableRecordIdentifier): void {
304
+ const recordData = this.#recordData;
305
+ if (this.#isDeprecated(recordData)) {
306
+ recordData.unloadRecord();
307
+ } else {
308
+ recordData.unloadRecord(identifier || this.#identifier);
309
+ }
310
+ }
311
+
312
+ // Attrs
313
+ // =====
314
+
315
+ /**
316
+ * Retrieve the data for an attribute from the cache
317
+ *
318
+ * @method getAttr
319
+ * @public
320
+ * @param identifier
321
+ * @param propertyName
322
+ * @returns {unknown}
323
+ */
324
+ getAttr(identifier: StableRecordIdentifier, propertyName: string): unknown {
325
+ // called by something V1
326
+ if (!isStableIdentifier(identifier)) {
327
+ propertyName = identifier;
328
+ identifier = this.#identifier;
329
+ }
330
+ let recordData = this.#recordData;
331
+ return this.#isDeprecated(recordData)
332
+ ? recordData.getAttr(propertyName)
333
+ : recordData.getAttr(identifier, propertyName);
334
+ }
335
+
336
+ /**
337
+ * Mutate the data for an attribute in the cache
338
+ *
339
+ * @method setAttr
340
+ * @public
341
+ * @param identifier
342
+ * @param propertyName
343
+ * @param value
344
+ */
345
+ setAttr(identifier: StableRecordIdentifier, propertyName: string, value: unknown): void {
346
+ let recordData = this.#recordData;
347
+
348
+ this.#isDeprecated(recordData)
349
+ ? recordData.setDirtyAttribute(propertyName, value)
350
+ : recordData.setAttr(identifier, propertyName, value);
351
+ }
352
+
353
+ /**
354
+ * Mutate the data for an attribute in the cache
355
+ *
356
+ * DEPRECATED use setAttr
357
+ *
358
+ * @method setDirtyAttribute
359
+ * @public
360
+ * @deprecated
361
+ * @param identifier
362
+ * @param propertyName
363
+ * @param value
364
+ */
365
+ setDirtyAttribute(propertyName: string, value: unknown): void {
366
+ let recordData = this.#recordData;
367
+
368
+ this.#isDeprecated(recordData)
369
+ ? recordData.setDirtyAttribute(propertyName, value)
370
+ : recordData.setAttr(this.#identifier, propertyName, value);
371
+ }
372
+
373
+ /**
374
+ * Query the cache for the changed attributes of a resource.
375
+ *
376
+ * DEPRECATED use changedAttrs
377
+ *
378
+ * @method changedAttributes
379
+ * @public
380
+ * @deprecated
381
+ * @param identifier
382
+ * @returns
383
+ */
384
+ changedAttributes(): ChangedAttributesHash {
385
+ const recordData = this.#recordData;
386
+ if (this.#isDeprecated(recordData)) {
387
+ return recordData.changedAttributes();
388
+ }
389
+ return recordData.changedAttrs(this.#identifier);
390
+ }
391
+
392
+ /**
393
+ * Query the cache for the changed attributes of a resource.
394
+ *
395
+ * @method changedAttrs
396
+ * @public
397
+ * @deprecated
398
+ * @param identifier
399
+ * @returns
400
+ */
401
+ changedAttrs(identifier: StableRecordIdentifier): ChangedAttributesHash {
402
+ const recordData = this.#recordData;
403
+ if (this.#isDeprecated(recordData)) {
404
+ return recordData.changedAttributes();
405
+ }
406
+ return recordData.changedAttrs(identifier);
407
+ }
408
+
409
+ /**
410
+ * Query the cache for whether any mutated attributes exist
411
+ *
412
+ * DEPRECATED use hasChangedAttrs
413
+ *
414
+ * @method hasChangedAttributes
415
+ * @public
416
+ * @deprecated
417
+ * @returns
418
+ */
419
+ hasChangedAttributes(): boolean {
420
+ const recordData = this.#recordData;
421
+ return this.#isDeprecated(recordData)
422
+ ? recordData.hasChangedAttributes()
423
+ : recordData.hasChangedAttrs(this.#identifier);
424
+ }
425
+
426
+ /**
427
+ * Query the cache for whether any mutated attributes exist
428
+ *
429
+ * @method hasChangedAttrs
430
+ * @public
431
+ * @param identifier
432
+ * @returns
433
+ */
434
+ hasChangedAttrs(identifier: StableRecordIdentifier): boolean {
435
+ const recordData = this.#recordData;
436
+ return this.#isDeprecated(recordData) ? recordData.hasChangedAttributes() : recordData.hasChangedAttrs(identifier);
437
+ }
438
+
439
+ /**
440
+ * Tell the cache to discard any uncommitted mutations to attributes
441
+ *
442
+ * DEPRECATED use rollbackAttrs
443
+ *
444
+ * @method rollbackAttributes
445
+ * @public
446
+ * @deprecated
447
+ * @returns
448
+ */
449
+ rollbackAttributes() {
450
+ const recordData = this.#recordData;
451
+ return this.#isDeprecated(recordData)
452
+ ? recordData.rollbackAttributes()
453
+ : recordData.rollbackAttrs(this.#identifier);
454
+ }
455
+
456
+ /**
457
+ * Tell the cache to discard any uncommitted mutations to attributes
458
+ *
459
+ * @method rollbackAttrs
460
+ * @public
461
+ * @param identifier
462
+ * @returns the names of attributes that were restored
463
+ */
464
+ rollbackAttrs(identifier: StableRecordIdentifier): string[] {
465
+ const recordData = this.#recordData;
466
+ return this.#isDeprecated(recordData) ? recordData.rollbackAttributes() : recordData.rollbackAttrs(identifier);
467
+ }
468
+
469
+ // Relationships
470
+ // =============
471
+
472
+ // the third arg here is "private". In a world with only V2 it is not necessary
473
+ // but in one in which we must convert a call from V2 -> V1 it is required to do this
474
+ // or else to do nasty schema lookup things
475
+ // @runspired has implemented this concept in relationships spikes and is confident
476
+ // we do not need any signal about whether a relationship is a collection or not at this
477
+ // boundary
478
+ /**
479
+ * Query the cache for the current state of a relationship property
480
+ *
481
+ * @method getRelationship
482
+ * @public
483
+ * @param identifier
484
+ * @param propertyName
485
+ * @returns resource relationship object
486
+ */
487
+ getRelationship(
488
+ identifier: StableRecordIdentifier,
489
+ propertyName: string,
490
+ isCollection = false
491
+ ): SingleResourceRelationship | CollectionResourceRelationship {
492
+ let recordData = this.#recordData;
493
+
494
+ if (this.#isDeprecated(recordData)) {
495
+ let isBelongsTo = !isCollection;
496
+ return isBelongsTo ? recordData.getBelongsTo(propertyName) : recordData.getHasMany(propertyName);
497
+ }
498
+
499
+ return recordData.getRelationship(identifier, propertyName);
500
+ }
501
+
502
+ /**
503
+ * Query the cache for the current state of a belongsTo field
504
+ *
505
+ * DEPRECATED use `getRelationship`
506
+ *
507
+ * @method getBelongsTo
508
+ * @public
509
+ * @deprecated
510
+ * @param propertyName
511
+ * @returns single resource relationship object
512
+ */
513
+ getBelongsTo(propertyName: string): SingleResourceRelationship {
514
+ let recordData = this.#recordData;
515
+
516
+ if (this.#isDeprecated(recordData)) {
517
+ return recordData.getBelongsTo(propertyName);
518
+ } else {
519
+ let identifier = this.#identifier;
520
+ return recordData.getRelationship(identifier, propertyName) as SingleResourceRelationship;
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Query the cache for the current state of a hasMany field
526
+ *
527
+ * DEPRECATED use `getRelationship`
528
+ *
529
+ * @method getHasMany
530
+ * @public
531
+ * @deprecated
532
+ * @param propertyName
533
+ * @returns single resource relationship object
534
+ */
535
+ getHasMany(propertyName: string): CollectionResourceRelationship {
536
+ let recordData = this.#recordData;
537
+
538
+ if (this.#isDeprecated(recordData)) {
539
+ return recordData.getHasMany(propertyName);
540
+ } else {
541
+ let identifier = this.#identifier;
542
+ return recordData.getRelationship(identifier, propertyName) as CollectionResourceRelationship;
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Mutate the current state of a belongsTo relationship
548
+ *
549
+ * DEPRECATED use update
550
+ *
551
+ * @method setDirtyBelongsTo
552
+ * @public
553
+ * @deprecated
554
+ * @param propertyName
555
+ * @param value
556
+ */
557
+ setDirtyBelongsTo(propertyName: string, value: NonSingletonRecordDataManager | null) {
558
+ const recordData = this.#recordData;
559
+
560
+ this.#isDeprecated(recordData)
561
+ ? recordData.setDirtyBelongsTo(propertyName, value)
562
+ : recordData.update({
563
+ op: 'replaceRelatedRecord',
564
+ record: this.#identifier,
565
+ field: propertyName,
566
+ value: value ? value.getResourceIdentifier() : null,
567
+ });
568
+ }
569
+
570
+ /**
571
+ * Mutate the current state of a hasMany relationship by adding values
572
+ * An index may optionally be specified which the cache should use for
573
+ * where in the list to insert the records
574
+ *
575
+ * DEPRECATED use update
576
+ *
577
+ * @method addToHasMany
578
+ * @deprecated
579
+ * @public
580
+ * @param propertyName
581
+ * @param value
582
+ * @param idx
583
+ */
584
+ addToHasMany(propertyName: string, value: NonSingletonRecordDataManager[], idx?: number): void {
585
+ const identifier = this.#identifier;
586
+ const recordData = this.#recordData;
587
+
588
+ this.#isDeprecated(recordData)
589
+ ? recordData.addToHasMany(propertyName, value, idx)
590
+ : recordData.update({
591
+ op: 'addToRelatedRecords',
592
+ field: propertyName,
593
+ record: identifier,
594
+ value: value.map((v) => v.getResourceIdentifier()),
595
+ });
596
+ }
597
+
598
+ /**
599
+ * Mutate the current state of a hasMany relationship by removing values.
600
+ *
601
+ * DEPRECATED use update
602
+ *
603
+ * @method removeFromHasMany
604
+ * @deprecated
605
+ * @public
606
+ * @param propertyName
607
+ * @param value
608
+ */
609
+ removeFromHasMany(propertyName: string, value: RecordData[]): void {
610
+ const identifier = this.#identifier;
611
+ const recordData = this.#recordData;
612
+
613
+ this.#isDeprecated(recordData)
614
+ ? recordData.removeFromHasMany(propertyName, value)
615
+ : recordData.update({
616
+ op: 'removeFromRelatedRecords',
617
+ record: identifier,
618
+ field: propertyName,
619
+ value: (value as unknown as NonSingletonRecordDataManager[]).map((v) => v.getResourceIdentifier()),
620
+ });
621
+ }
622
+
623
+ /**
624
+ * Mutate the current state of a hasMany relationship by replacing it entirely
625
+ *
626
+ * DEPRECATED use `setHasMany`
627
+ *
628
+ * @method setDirtyHasMany
629
+ * @public
630
+ * @deprecated
631
+ * @param propertyName
632
+ * @param value
633
+ */
634
+ setDirtyHasMany(propertyName: string, value: NonSingletonRecordDataManager[]) {
635
+ let recordData = this.#recordData;
636
+
637
+ this.#isDeprecated(recordData)
638
+ ? recordData.setDirtyHasMany(propertyName, value)
639
+ : recordData.update({
640
+ op: 'replaceRelatedRecords',
641
+ record: this.#identifier,
642
+ field: propertyName,
643
+ value: value.map((rd) => rd.getResourceIdentifier()),
644
+ });
645
+ }
646
+
647
+ // State
648
+ // =============
649
+
650
+ /**
651
+ * Update the cache state for the given resource to be marked as locally deleted,
652
+ * or remove such a mark.
653
+ *
654
+ * @method setIsDeleted
655
+ * @public
656
+ * @param identifier
657
+ * @param isDeleted
658
+ */
659
+ setIsDeleted(identifier: StableRecordIdentifier, isDeleted: boolean): void {
660
+ if (!isStableIdentifier(identifier)) {
661
+ isDeleted = identifier as boolean;
662
+ identifier = this.#identifier;
663
+ }
664
+ const recordData = this.#recordData;
665
+ this.#isDeprecated(recordData)
666
+ ? recordData.setIsDeleted(isDeleted)
667
+ : recordData.setIsDeleted(identifier, isDeleted);
668
+ }
669
+
670
+ /**
671
+ * Query the cache for any validation errors applicable to the given resource.
672
+ *
673
+ * @method getErrors
674
+ * @public
675
+ * @param identifier
676
+ * @returns
677
+ */
678
+ getErrors(identifier: StableRecordIdentifier): JsonApiValidationError[] {
679
+ return this.#recordData.getErrors(identifier || this.#identifier);
680
+ }
681
+
682
+ /**
683
+ * Query the cache for whether a given resource has any available data
684
+ *
685
+ * @method isEmpty
686
+ * @public
687
+ * @param identifier
688
+ * @returns {boolean}
689
+ */
690
+ isEmpty(identifier: StableRecordIdentifier): boolean {
691
+ const recordData = this.#recordData;
692
+ return this.#isDeprecated(recordData)
693
+ ? recordData.isEmpty?.(identifier || this.#identifier) || false
694
+ : recordData.isEmpty(identifier || this.#identifier);
695
+ }
696
+
697
+ /**
698
+ * Query the cache for whether a given resource was created locally and not
699
+ * yet persisted.
700
+ *
701
+ * @method isNew
702
+ * @public
703
+ * @param identifier
704
+ * @returns {boolean}
705
+ */
706
+ isNew(identifier: StableRecordIdentifier): boolean {
707
+ return this.#recordData.isNew(identifier || this.#identifier);
708
+ }
709
+
710
+ /**
711
+ * Query the cache for whether a given resource is marked as deleted (but not
712
+ * necessarily persisted yet).
713
+ *
714
+ * @method isDeleted
715
+ * @public
716
+ * @param identifier
717
+ * @returns {boolean}
718
+ */
719
+ isDeleted(identifier: StableRecordIdentifier): boolean {
720
+ return this.#recordData.isDeleted(identifier || this.#identifier);
721
+ }
722
+
723
+ /**
724
+ * Query the cache for whether a given resource has been deleted and that deletion
725
+ * has also been persisted.
726
+ *
727
+ * @method isDeletionCommitted
728
+ * @public
729
+ * @param identifier
730
+ * @returns {boolean}
731
+ */
732
+ isDeletionCommitted(identifier: StableRecordIdentifier): boolean {
733
+ return this.#recordData.isDeletionCommitted(identifier || this.#identifier);
734
+ }
735
+ }
736
+
737
+ export class SingletonRecordDataManager implements RecordData {
738
+ version: '2' = '2';
739
+
740
+ #recordDatas: Map<StableRecordIdentifier, RecordData>;
741
+
742
+ constructor() {
743
+ this.#recordDatas = new Map();
744
+ }
745
+
746
+ _addRecordData(identifier: StableRecordIdentifier, recordData: RecordData) {
747
+ this.#recordDatas.set(identifier, recordData);
748
+ }
749
+
750
+ #recordData(identifier: StableRecordIdentifier): RecordData {
751
+ assert(`No RecordData Yet Exists!`, this.#recordDatas.has(identifier));
752
+ return this.#recordDatas.get(identifier)!;
753
+ }
754
+
755
+ // Cache
756
+ // =====
757
+
758
+ pushData(identifier: StableRecordIdentifier, data: JsonApiResource, hasRecord?: boolean): void | string[] {
759
+ return this.#recordData(identifier).pushData(identifier, data, hasRecord);
760
+ }
761
+
762
+ sync(op: MergeOperation): void {
763
+ this.#recordData(op.record).sync(op);
764
+ }
765
+
766
+ clientDidCreate(identifier: StableRecordIdentifier, options?: Dict<unknown>): Dict<unknown> {
767
+ return this.#recordData(identifier).clientDidCreate(identifier, options);
768
+ }
769
+
770
+ willCommit(identifier: StableRecordIdentifier): void {
771
+ this.#recordData(identifier).willCommit(identifier);
772
+ }
773
+
774
+ didCommit(identifier: StableRecordIdentifier, data: JsonApiResource | null): void {
775
+ this.#recordData(identifier).didCommit(identifier, data);
776
+ }
777
+
778
+ commitWasRejected(identifier: StableRecordIdentifier, errors?: JsonApiValidationError[]): void {
779
+ this.#recordData(identifier).commitWasRejected(identifier, errors);
780
+ }
781
+
782
+ unloadRecord(identifier: StableRecordIdentifier): void {
783
+ this.#recordData(identifier).unloadRecord(identifier);
784
+ }
785
+
786
+ // Attrs
787
+ // =====
788
+
789
+ getAttr(identifier: StableRecordIdentifier, propertyName: string): unknown {
790
+ return this.#recordData(identifier).getAttr(identifier, propertyName);
791
+ }
792
+
793
+ setAttr(identifier: StableRecordIdentifier, propertyName: string, value: unknown): void {
794
+ this.#recordData(identifier).setAttr(identifier, propertyName, value);
795
+ }
796
+
797
+ changedAttrs(identifier: StableRecordIdentifier): ChangedAttributesHash {
798
+ return this.#recordData(identifier).changedAttrs(identifier);
799
+ }
800
+
801
+ hasChangedAttrs(identifier: StableRecordIdentifier): boolean {
802
+ return this.#recordData(identifier).hasChangedAttrs(identifier);
803
+ }
804
+
805
+ rollbackAttrs(identifier: StableRecordIdentifier): string[] {
806
+ return this.#recordData(identifier).rollbackAttrs(identifier);
807
+ }
808
+
809
+ getRelationship(
810
+ identifier: StableRecordIdentifier,
811
+ propertyName: string
812
+ ): SingleResourceRelationship | CollectionResourceRelationship {
813
+ return this.#recordData(identifier).getRelationship(identifier, propertyName);
814
+ }
815
+ update(operation: LocalRelationshipOperation): void {
816
+ this.#recordData(operation.record).update(operation);
817
+ }
818
+
819
+ // State
820
+ // =============
821
+
822
+ setIsDeleted(identifier: StableRecordIdentifier, isDeleted: boolean): void {
823
+ this.#recordData(identifier).setIsDeleted(identifier, isDeleted);
824
+ }
825
+
826
+ getErrors(identifier: StableRecordIdentifier): JsonApiValidationError[] {
827
+ return this.#recordData(identifier).getErrors(identifier);
828
+ }
829
+
830
+ isEmpty(identifier: StableRecordIdentifier): boolean {
831
+ return this.#recordData(identifier).isEmpty(identifier);
832
+ }
833
+
834
+ isNew(identifier: StableRecordIdentifier): boolean {
835
+ return this.#recordData(identifier).isNew(identifier);
836
+ }
837
+
838
+ isDeleted(identifier: StableRecordIdentifier): boolean {
839
+ return this.#recordData(identifier).isDeleted(identifier);
840
+ }
841
+
842
+ isDeletionCommitted(identifier: StableRecordIdentifier): boolean {
843
+ return this.#recordData(identifier).isDeletionCommitted(identifier);
844
+ }
845
+ }