@ember-data/store 4.8.0-alpha.5 → 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.
@@ -1,13 +1,17 @@
1
- import { assert } from '@ember/debug';
2
-
3
- import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
4
- import { StableRecordIdentifier } from '@ember-data/types/q/identifier';
5
- import { ChangedAttributesHash, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
6
- import { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
7
- import { Dict } from '@ember-data/types/q/utils';
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, 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';
8
12
 
9
13
  import { isStableIdentifier } from '../caches/identifier-cache';
10
- import Store from '../store-service';
14
+ import type Store from '../store-service';
11
15
 
12
16
  /**
13
17
  * The RecordDataManager wraps a RecordData cache
@@ -64,6 +68,19 @@ export class NonSingletonRecordDataManager implements RecordData {
64
68
  this.#store = store;
65
69
  this.#recordData = recordData;
66
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
+ }
67
84
  }
68
85
 
69
86
  #isDeprecated(recordData: RecordData | RecordDataV1): recordData is RecordDataV1 {
@@ -111,6 +128,59 @@ export class NonSingletonRecordDataManager implements RecordData {
111
128
  return recordData.pushData(identifier, data, hasRecord);
112
129
  }
113
130
 
131
+ /**
132
+ * Update resource data with a local mutation. Currently supports operations
133
+ * on relationships only.
134
+ *
135
+ * @method update
136
+ * @public
137
+ * @param operation
138
+ */
139
+ // isCollection is only needed for interop with v1 cache
140
+ update(operation: LocalRelationshipOperation, isResource?: boolean): void {
141
+ if (this.#isDeprecated(this.#recordData)) {
142
+ const cache = this.#store._instanceCache;
143
+ switch (operation.op) {
144
+ case 'addToRelatedRecords':
145
+ this.#recordData.addToHasMany(
146
+ operation.field,
147
+ (operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i)),
148
+ operation.index
149
+ );
150
+ return;
151
+ case 'removeFromRelatedRecords':
152
+ this.#recordData.removeFromHasMany(
153
+ operation.field,
154
+ (operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i))
155
+ );
156
+ return;
157
+ case 'replaceRelatedRecords':
158
+ this.#recordData.setDirtyHasMany(
159
+ operation.field,
160
+ operation.value.map((i) => cache.getRecordData(i))
161
+ );
162
+ return;
163
+ case 'replaceRelatedRecord':
164
+ if (isResource) {
165
+ this.#recordData.setDirtyBelongsTo(
166
+ operation.field,
167
+ operation.value ? cache.getRecordData(operation.value) : null
168
+ );
169
+ return;
170
+ }
171
+ this.#recordData.removeFromHasMany(operation.field, [cache.getRecordData(operation.prior!)]);
172
+ this.#recordData.addToHasMany(operation.field, [cache.getRecordData(operation.value!)], operation.index);
173
+ return;
174
+ case 'sortRelatedRecords':
175
+ return;
176
+ default:
177
+ return;
178
+ }
179
+ } else {
180
+ this.#recordData.update(operation);
181
+ }
182
+ }
183
+
114
184
  /**
115
185
  * [LIFECYLCE] Signal to the cache that a new record has been instantiated on the client
116
186
  *
@@ -457,25 +527,7 @@ export class NonSingletonRecordDataManager implements RecordData {
457
527
  /**
458
528
  * Mutate the current state of a belongsTo relationship
459
529
  *
460
- * @method setBelongsTo
461
- * @public
462
- * @param identifier
463
- * @param propertyName
464
- * @param value
465
- */
466
- setBelongsTo(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier | null) {
467
- const store = this.#store;
468
- const recordData = this.#recordData;
469
-
470
- this.#isDeprecated(recordData)
471
- ? recordData.setDirtyBelongsTo(propertyName, value ? store._instanceCache.getRecordData(value) : null)
472
- : recordData.setBelongsTo(identifier, propertyName, value);
473
- }
474
-
475
- /**
476
- * Mutate the current state of a belongsTo relationship
477
- *
478
- * DEPRECATED use setBelongsTo
530
+ * DEPRECATED use update
479
531
  *
480
532
  * @method setDirtyBelongsTo
481
533
  * @public
@@ -487,8 +539,13 @@ export class NonSingletonRecordDataManager implements RecordData {
487
539
  const recordData = this.#recordData;
488
540
 
489
541
  this.#isDeprecated(recordData)
490
- ? recordData.setDirtyBelongsTo(propertyName, value as unknown as RecordData)
491
- : recordData.setBelongsTo(this.#identifier, propertyName, value ? value.getResourceIdentifier() : null);
542
+ ? recordData.setDirtyBelongsTo(propertyName, value)
543
+ : recordData.update({
544
+ op: 'replaceRelatedRecord',
545
+ record: this.#identifier,
546
+ field: propertyName,
547
+ value: value ? value.getResourceIdentifier() : null,
548
+ });
492
549
  }
493
550
 
494
551
  /**
@@ -496,98 +553,52 @@ export class NonSingletonRecordDataManager implements RecordData {
496
553
  * An index may optionally be specified which the cache should use for
497
554
  * where in the list to insert the records
498
555
  *
556
+ * DEPRECATED use update
557
+ *
499
558
  * @method addToHasMany
559
+ * @deprecated
500
560
  * @public
501
- * @param identifier
502
561
  * @param propertyName
503
562
  * @param value
504
563
  * @param idx
505
564
  */
506
- addToHasMany(
507
- identifier: StableRecordIdentifier,
508
- propertyName: string,
509
- value: StableRecordIdentifier[],
510
- idx?: number
511
- ): void {
512
- // called by something V1
513
- let isFromV1 = false;
514
- if (!isStableIdentifier(identifier)) {
515
- isFromV1 = true;
516
- idx = value as unknown as number;
517
- value = propertyName as unknown as StableRecordIdentifier[];
518
- propertyName = identifier as unknown as string;
519
- identifier = this.#identifier;
520
- }
521
- const cache = this.#store._instanceCache;
565
+ addToHasMany(propertyName: string, value: NonSingletonRecordDataManager[], idx?: number): void {
566
+ const identifier = this.#identifier;
522
567
  const recordData = this.#recordData;
523
568
 
524
569
  this.#isDeprecated(recordData)
525
- ? recordData.addToHasMany(
526
- propertyName,
527
- isFromV1 ? (value as unknown as RecordData[]) : value.map((v) => cache.getRecordData(v)),
528
- idx
529
- )
530
- : recordData.addToHasMany(
531
- identifier,
532
- propertyName,
533
- isFromV1
534
- ? (value as unknown as NonSingletonRecordDataManager[]).map((v) => v.getResourceIdentifier())
535
- : value,
536
- idx
537
- );
570
+ ? recordData.addToHasMany(propertyName, value, idx)
571
+ : recordData.update({
572
+ op: 'addToRelatedRecords',
573
+ field: propertyName,
574
+ record: identifier,
575
+ value: value.map((v) => v.getResourceIdentifier()),
576
+ });
538
577
  }
539
578
 
540
579
  /**
541
580
  * Mutate the current state of a hasMany relationship by removing values.
542
581
  *
543
- * @method removeFromHasMany
544
- * @public
545
- * @param identifier
546
- * @param propertyName
547
- * @param value
548
- */
549
- removeFromHasMany(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier[]): void {
550
- let isFromV1 = false;
551
- if (!isStableIdentifier(identifier)) {
552
- isFromV1 = true;
553
- value = propertyName as unknown as StableRecordIdentifier[];
554
- propertyName = identifier as unknown as string;
555
- identifier = this.#identifier;
556
- }
557
- const cache = this.#store._instanceCache;
558
- const recordData = this.#recordData;
559
-
560
- this.#isDeprecated(recordData)
561
- ? recordData.removeFromHasMany(
562
- propertyName,
563
- isFromV1 ? (value as unknown as RecordData[]) : value.map((v) => cache.getRecordData(v))
564
- )
565
- : recordData.removeFromHasMany(
566
- identifier,
567
- propertyName,
568
- isFromV1 ? (value as unknown as NonSingletonRecordDataManager[]).map((v) => v.getResourceIdentifier()) : value
569
- );
570
- }
571
-
572
- /**
573
- * Mutate the current state of a hasMany relationship by replacing it entirely
582
+ * DEPRECATED use update
574
583
  *
575
- * @method setHasMany
584
+ * @method removeFromHasMany
585
+ * @deprecated
576
586
  * @public
577
- * @param identifier
578
587
  * @param propertyName
579
588
  * @param value
580
589
  */
581
- setHasMany(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier[]): void {
582
- const cache = this.#store._instanceCache;
590
+ removeFromHasMany(propertyName: string, value: RecordData[]): void {
591
+ const identifier = this.#identifier;
583
592
  const recordData = this.#recordData;
584
593
 
585
594
  this.#isDeprecated(recordData)
586
- ? recordData.setDirtyHasMany(
587
- propertyName,
588
- value.map((identifier) => cache.getRecordData(identifier))
589
- )
590
- : recordData.setHasMany(identifier, propertyName, value);
595
+ ? recordData.removeFromHasMany(propertyName, value)
596
+ : recordData.update({
597
+ op: 'removeFromRelatedRecords',
598
+ record: identifier,
599
+ field: propertyName,
600
+ value: (value as unknown as NonSingletonRecordDataManager[]).map((v) => v.getResourceIdentifier()),
601
+ });
591
602
  }
592
603
 
593
604
  /**
@@ -601,16 +612,17 @@ export class NonSingletonRecordDataManager implements RecordData {
601
612
  * @param propertyName
602
613
  * @param value
603
614
  */
604
- setDirtyHasMany(propertyName: string, value: RecordData[]) {
615
+ setDirtyHasMany(propertyName: string, value: NonSingletonRecordDataManager[]) {
605
616
  let recordData = this.#recordData;
606
617
 
607
618
  this.#isDeprecated(recordData)
608
619
  ? recordData.setDirtyHasMany(propertyName, value)
609
- : recordData.setHasMany(
610
- this.#identifier,
611
- propertyName,
612
- (value as unknown as NonSingletonRecordDataManager[]).map((rd) => rd.getResourceIdentifier())
613
- );
620
+ : recordData.update({
621
+ op: 'replaceRelatedRecords',
622
+ record: this.#identifier,
623
+ field: propertyName,
624
+ value: value.map((rd) => rd.getResourceIdentifier()),
625
+ });
614
626
  }
615
627
 
616
628
  // State
@@ -706,11 +718,9 @@ export class NonSingletonRecordDataManager implements RecordData {
706
718
  export class SingletonRecordDataManager implements RecordData {
707
719
  version: '2' = '2';
708
720
 
709
- #store: Store;
710
721
  #recordDatas: Map<StableRecordIdentifier, RecordData>;
711
722
 
712
- constructor(store: Store) {
713
- this.#store = store;
723
+ constructor() {
714
724
  this.#recordDatas = new Map();
715
725
  }
716
726
 
@@ -779,26 +789,8 @@ export class SingletonRecordDataManager implements RecordData {
779
789
  ): SingleResourceRelationship | CollectionResourceRelationship {
780
790
  return this.#recordData(identifier).getRelationship(identifier, propertyName);
781
791
  }
782
-
783
- setBelongsTo(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier | null) {
784
- this.#recordData(identifier).setBelongsTo(identifier, propertyName, value);
785
- }
786
-
787
- addToHasMany(
788
- identifier: StableRecordIdentifier,
789
- propertyName: string,
790
- value: StableRecordIdentifier[],
791
- idx?: number
792
- ): void {
793
- this.#recordData(identifier).addToHasMany(identifier, propertyName, value, idx);
794
- }
795
-
796
- removeFromHasMany(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier[]): void {
797
- this.#recordData(identifier).removeFromHasMany(identifier, propertyName, value);
798
- }
799
-
800
- setHasMany(identifier: StableRecordIdentifier, propertyName: string, value: StableRecordIdentifier[]): void {
801
- this.#recordData(identifier).setHasMany(identifier, propertyName, value);
792
+ update(operation: LocalRelationshipOperation): void {
793
+ this.#recordData(operation.record).update(operation);
802
794
  }
803
795
 
804
796
  // State
@@ -350,7 +350,13 @@ class V2RecordDataStoreWrapper implements StoreWrapper {
350
350
  }
351
351
 
352
352
  this._willNotify = true;
353
- this._store._schedule('notify', () => this._flushNotifications());
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();
359
+ }
354
360
  }
355
361
 
356
362
  _flushNotifications(): void {
@@ -53,6 +53,7 @@ export default class NotificationManager {
53
53
  map = new Map();
54
54
  Cache.set(identifier, map);
55
55
  }
56
+
56
57
  let unsubToken = DEBUG ? { _tokenRef: tokenId++ } : {};
57
58
  map.set(unsubToken, callback);
58
59
  Tokens.set(unsubToken, identifier);
@@ -5,8 +5,10 @@ import { assert, deprecate, warn } from '@ember/debug';
5
5
  import { _backburner as emberBackburner } from '@ember/runloop';
6
6
  import { DEBUG } from '@glimmer/env';
7
7
 
8
+ import { importSync } from '@embroider/macros';
8
9
  import { default as RSVP, resolve } from 'rsvp';
9
10
 
11
+ import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
10
12
  import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';
11
13
  import type { CollectionResourceDocument, SingleResourceDocument } from '@ember-data/types/q/ember-data-json-api';
12
14
  import type { FindRecordQuery, Request, SaveRecordMutation } from '@ember-data/types/q/fetch-manager';
@@ -214,7 +216,20 @@ export default class FetchManager {
214
216
  (error) => {
215
217
  const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' });
216
218
  if (!recordData || recordData.isEmpty(identifier) || isLoading) {
217
- store._instanceCache.unloadRecord(identifier);
219
+ let isReleasable = true;
220
+ if (!recordData && HAS_RECORD_DATA_PACKAGE) {
221
+ const graphFor = (
222
+ importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
223
+ ).graphFor;
224
+ const graph = graphFor(store);
225
+ isReleasable = graph.isReleasable(identifier);
226
+ if (!isReleasable) {
227
+ graph.unload(identifier, true);
228
+ }
229
+ }
230
+ if (recordData || isReleasable) {
231
+ store._instanceCache.unloadRecord(identifier);
232
+ }
218
233
  }
219
234
  throw error;
220
235
  }
@@ -10,7 +10,8 @@ import { StableRecordIdentifier } from '@ember-data/types/q/identifier';
10
10
  import type { FindOptions } from '@ember-data/types/q/store';
11
11
  import type { Dict } from '@ember-data/types/q/utils';
12
12
 
13
- import type RecordArray from '../record-arrays/record-array';
13
+ import type IdentifierArray from '../record-arrays/identifier-array';
14
+ import { SOURCE } from '../record-arrays/identifier-array';
14
15
  import Store from '../store-service';
15
16
  import type Snapshot from './snapshot';
16
17
  /**
@@ -23,12 +24,11 @@ import type Snapshot from './snapshot';
23
24
  */
24
25
  export default class SnapshotRecordArray {
25
26
  declare _snapshots: Snapshot[] | null;
26
- declare _recordArray: RecordArray;
27
+ declare _recordArray: IdentifierArray;
27
28
  declare _type: ModelSchema | null;
28
29
  declare __store: Store;
29
30
 
30
31
  declare length: number;
31
- declare meta: Dict<unknown> | null;
32
32
  declare adapterOptions?: Dict<unknown>;
33
33
  declare include?: string;
34
34
 
@@ -41,10 +41,9 @@ export default class SnapshotRecordArray {
41
41
  @private
42
42
  @constructor
43
43
  @param {RecordArray} recordArray
44
- @param {Object} meta
45
44
  @param options
46
45
  */
47
- constructor(store: Store, recordArray: RecordArray, options: FindOptions = {}) {
46
+ constructor(store: Store, recordArray: IdentifierArray, options: FindOptions = {}) {
48
47
  this.__store = store;
49
48
  /**
50
49
  An array of snapshots
@@ -181,8 +180,9 @@ export default class SnapshotRecordArray {
181
180
  if (this._snapshots !== null) {
182
181
  return this._snapshots;
183
182
  }
183
+
184
184
  const { _instanceCache } = this.__store;
185
- this._snapshots = this._recordArray.content.map((identifier: StableRecordIdentifier) =>
185
+ this._snapshots = this._recordArray[SOURCE].map((identifier: StableRecordIdentifier) =>
186
186
  _instanceCache.createSnapshot(identifier)
187
187
  );
188
188
 
@@ -91,13 +91,13 @@ export class PromiseArray<I, T extends EmberArrayLike<I>> extends PromiseArrayPr
91
91
  */
92
92
  export { PromiseObjectProxy as PromiseObject };
93
93
 
94
- export function promiseObject<T>(promise: Promise<T>, label?: string): PromiseObjectProxy<T> {
94
+ function _promiseObject<T>(promise: Promise<T>, label?: string): PromiseObjectProxy<T> {
95
95
  return PromiseObjectProxy.create({
96
96
  promise: resolve(promise, label),
97
97
  }) as PromiseObjectProxy<T>;
98
98
  }
99
99
 
100
- export function promiseArray<I, T extends EmberArrayLike<I>>(promise: Promise<T>, label?: string): PromiseArray<I, T> {
100
+ function _promiseArray<I, T extends EmberArrayLike<I>>(promise: Promise<T>, label?: string): PromiseArray<I, T> {
101
101
  return PromiseArray.create({
102
102
  promise: resolve(promise, label),
103
103
  }) as unknown as PromiseArray<I, T>;
@@ -105,35 +105,96 @@ export function promiseArray<I, T extends EmberArrayLike<I>>(promise: Promise<T>
105
105
 
106
106
  // constructor is accessed in some internals but not including it in the copyright for the deprecation
107
107
  const ALLOWABLE_METHODS = ['constructor', 'then', 'catch', 'finally'];
108
+ const PROXIED_ARRAY_PROPS = [
109
+ 'length',
110
+ '[]',
111
+ 'firstObject',
112
+ 'lastObject',
113
+ 'meta',
114
+ 'content',
115
+ 'isPending',
116
+ 'isSettled',
117
+ 'isRejected',
118
+ 'isFulfilled',
119
+ 'promise',
120
+ 'reason',
121
+ ];
122
+ const PROXIED_OBJECT_PROPS = ['content', 'isPending', 'isSettled', 'isRejected', 'isFulfilled', 'promise', 'reason'];
123
+
124
+ export function promiseArray<I, T extends EmberArrayLike<I>>(promise: Promise<T>): PromiseArray<I, T> {
125
+ const promiseObjectProxy: PromiseArray<I, T> = _promiseArray(promise);
126
+ const handler = {
127
+ get(target: object, prop: string, receiver?: object): unknown {
128
+ if (typeof prop === 'symbol') {
129
+ return Reflect.get(target, prop, receiver);
130
+ }
131
+ if (!ALLOWABLE_METHODS.includes(prop)) {
132
+ deprecate(
133
+ `Accessing ${prop} on this PromiseArray is deprecated. The return type is being changed from PromiseArray to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
134
+ false,
135
+ {
136
+ id: 'ember-data:deprecate-promise-proxies',
137
+ until: '5.0',
138
+ for: '@ember-data/store',
139
+ since: {
140
+ available: '4.8',
141
+ enabled: '4.8',
142
+ },
143
+ }
144
+ );
145
+ }
108
146
 
109
- export function deprecatedPromiseObject<T>(promise: Promise<T>): PromiseObjectProxy<T> {
110
- const promiseObjectProxy: PromiseObjectProxy<T> = promiseObject(promise);
147
+ const value: unknown = target[prop];
148
+ if (value && typeof value === 'function' && typeof value.bind === 'function') {
149
+ return value.bind(target);
150
+ }
151
+
152
+ if (PROXIED_ARRAY_PROPS.includes(prop)) {
153
+ return value;
154
+ }
155
+
156
+ return undefined;
157
+ },
158
+ };
159
+
160
+ return new Proxy(promiseObjectProxy, handler);
161
+ }
162
+
163
+ export function promiseObject<T>(promise: Promise<T>): PromiseObjectProxy<T> {
164
+ const promiseObjectProxy: PromiseObjectProxy<T> = _promiseObject(promise);
111
165
  const handler = {
112
166
  get(target: object, prop: string, receiver?: object): unknown {
167
+ if (typeof prop === 'symbol') {
168
+ return Reflect.get(target, prop, receiver);
169
+ }
113
170
  if (!ALLOWABLE_METHODS.includes(prop)) {
114
171
  deprecate(
115
- `Accessing ${prop} is deprecated. The return type is being changed fomr PromiseObjectProxy to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
172
+ `Accessing ${prop} on this PromiseObject is deprecated. The return type is being changed from PromiseObject to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
116
173
  false,
117
174
  {
118
- id: 'ember-data:model-save-promise',
175
+ id: 'ember-data:deprecate-promise-proxies',
119
176
  until: '5.0',
120
177
  for: '@ember-data/store',
121
178
  since: {
122
- available: '4.4',
123
- enabled: '4.4',
179
+ available: '4.8',
180
+ enabled: '4.8',
124
181
  },
125
182
  }
126
183
  );
127
184
  }
128
185
 
129
- const value: unknown = Reflect.get(target, prop, receiver);
186
+ const value: unknown = target[prop];
130
187
  if (value && typeof value === 'function' && typeof value.bind === 'function') {
131
188
  return value.bind(target);
132
189
  }
133
190
 
134
- return value;
191
+ if (PROXIED_OBJECT_PROPS.includes(prop)) {
192
+ return value;
193
+ }
194
+
195
+ return undefined;
135
196
  },
136
197
  };
137
198
 
138
- return new Proxy(promiseObjectProxy, handler) as PromiseObjectProxy<T>;
199
+ return new Proxy(promiseObjectProxy, handler);
139
200
  }