@ember-data/model 4.8.0-alpha.2 → 4.8.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import ArrayMixin, { NativeArray } from '@ember/array';
2
2
  import type ArrayProxy from '@ember/array/proxy';
3
- import { assert } from '@ember/debug';
3
+ import { assert, deprecate } from '@ember/debug';
4
4
  import { dependentKeyCompat } from '@ember/object/compat';
5
5
  import { tracked } from '@glimmer/tracking';
6
6
  import Ember from 'ember';
@@ -9,8 +9,10 @@ import { resolve } from 'rsvp';
9
9
 
10
10
  import type { ManyArray } from 'ember-data/-private';
11
11
 
12
- import type { InternalModel } from '@ember-data/store/-private';
12
+ import { DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS } from '@ember-data/private-build-infra/deprecations';
13
+ import { StableRecordIdentifier } from '@ember-data/types/q/identifier';
13
14
  import type { RecordInstance } from '@ember-data/types/q/record-instance';
15
+ import { FindOptions } from '@ember-data/types/q/store';
14
16
 
15
17
  export interface HasManyProxyCreateArgs {
16
18
  promise: Promise<ManyArray>;
@@ -24,16 +26,10 @@ export interface HasManyProxyCreateArgs {
24
26
  This class is returned as the result of accessing an async hasMany relationship
25
27
  on an instance of a Model extending from `@ember-data/model`.
26
28
 
27
- A PromiseManyArray is an array-like proxy that also proxies certain method calls
28
- to the underlying ManyArray in addition to being "promisified".
29
+ A PromiseManyArray is an iterable proxy that allows templates to consume related
30
+ ManyArrays and update once their contents are no longer pending.
29
31
 
30
- Right now we proxy:
31
-
32
- * `reload()`
33
- * `createRecord()`
34
-
35
- This promise-proxy behavior is primarily to ensure that async relationship interact
36
- nicely with templates. In your JS code you should resolve the promise first.
32
+ In your JS code you should resolve the promise first.
37
33
 
38
34
  ```js
39
35
  const comments = await post.comments;
@@ -42,10 +38,14 @@ export interface HasManyProxyCreateArgs {
42
38
  @class PromiseManyArray
43
39
  @public
44
40
  */
45
- export default interface PromiseManyArray extends Omit<ArrayProxy<InternalModel, RecordInstance>, 'destroy'> {}
41
+ export default interface PromiseManyArray extends Omit<ArrayProxy<StableRecordIdentifier, RecordInstance>, 'destroy'> {
42
+ createRecord(): RecordInstance;
43
+ reload(options: FindOptions): PromiseManyArray;
44
+ }
46
45
  export default class PromiseManyArray {
47
46
  declare promise: Promise<ManyArray> | null;
48
47
  declare isDestroyed: boolean;
48
+ // @deprecated (isDestroyed is not deprecated)
49
49
  declare isDestroying: boolean;
50
50
 
51
51
  constructor(promise: Promise<ManyArray>, content?: ManyArray) {
@@ -90,6 +90,8 @@ export default class PromiseManyArray {
90
90
 
91
91
  /**
92
92
  * Iterate the proxied content. Called by the glimmer iterator in #each
93
+ * We do not guarantee that forEach will always be available. This
94
+ * may eventually be made to use Symbol.Iterator once glimmer supports it.
93
95
  *
94
96
  * @method forEach
95
97
  * @param cb
@@ -103,6 +105,19 @@ export default class PromiseManyArray {
103
105
  }
104
106
  }
105
107
 
108
+ /**
109
+ * Reload the relationship
110
+ * @method reload
111
+ * @public
112
+ * @param options
113
+ * @returns
114
+ */
115
+ reload(options: FindOptions) {
116
+ assert('You are trying to reload an async manyArray before it has been created', this.content);
117
+ this.content.reload(options);
118
+ return this;
119
+ }
120
+
106
121
  //---- Properties/Methods from the PromiseProxyMixin that we will keep as our API
107
122
 
108
123
  /**
@@ -201,19 +216,6 @@ export default class PromiseManyArray {
201
216
  return this.content ? this.content.meta : undefined;
202
217
  }
203
218
 
204
- /**
205
- * Reload the relationship
206
- * @method reload
207
- * @public
208
- * @param options
209
- * @returns
210
- */
211
- reload(options) {
212
- assert('You are trying to reload an async manyArray before it has been created', this.content);
213
- this.content.reload(options);
214
- return this;
215
- }
216
-
217
219
  //---- Our own stuff
218
220
 
219
221
  _update(promise: Promise<ManyArray>, content?: ManyArray) {
@@ -227,22 +229,55 @@ export default class PromiseManyArray {
227
229
  static create({ promise, content }: HasManyProxyCreateArgs): PromiseManyArray {
228
230
  return new this(promise, content);
229
231
  }
232
+ }
230
233
 
231
- // Methods on ManyArray which people should resolve the relationship first before calling
232
- createRecord(...args) {
234
+ if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) {
235
+ PromiseManyArray.prototype.createRecord = function createRecord(...args) {
236
+ deprecate(
237
+ `The createRecord method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
238
+ false,
239
+ {
240
+ id: 'ember-data:deprecate-promise-many-array-behaviors',
241
+ until: '5.0',
242
+ since: { enabled: '4.8', available: '4.8' },
243
+ for: 'ember-data',
244
+ }
245
+ );
233
246
  assert('You are trying to createRecord on an async manyArray before it has been created', this.content);
234
247
  return this.content.createRecord(...args);
235
- }
236
-
237
- // Properties/Methods on ArrayProxy we should deprecate
238
-
239
- get firstObject() {
240
- return this.content ? this.content.firstObject : undefined;
241
- }
248
+ };
242
249
 
243
- get lastObject() {
244
- return this.content ? this.content.lastObject : undefined;
245
- }
250
+ Object.defineProperty(PromiseManyArray.prototype, 'firstObject', {
251
+ get() {
252
+ deprecate(
253
+ `The firstObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
254
+ false,
255
+ {
256
+ id: 'ember-data:deprecate-promise-many-array-behaviors',
257
+ until: '5.0',
258
+ since: { enabled: '4.8', available: '4.8' },
259
+ for: 'ember-data',
260
+ }
261
+ );
262
+ return this.content ? this.content.firstObject : undefined;
263
+ },
264
+ });
265
+
266
+ Object.defineProperty(PromiseManyArray.prototype, 'lastObject', {
267
+ get() {
268
+ deprecate(
269
+ `The lastObject property on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
270
+ false,
271
+ {
272
+ id: 'ember-data:deprecate-promise-many-array-behaviors',
273
+ until: '5.0',
274
+ since: { enabled: '4.8', available: '4.8' },
275
+ for: 'ember-data',
276
+ }
277
+ );
278
+ return this.content ? this.content.lastObject : undefined;
279
+ },
280
+ });
246
281
  }
247
282
 
248
283
  function tapPromise(proxy: PromiseManyArray, promise: Promise<ManyArray>) {
@@ -268,78 +303,101 @@ function tapPromise(proxy: PromiseManyArray, promise: Promise<ManyArray>) {
268
303
  );
269
304
  }
270
305
 
271
- const EmberObjectMethods = [
272
- 'addObserver',
273
- 'cacheFor',
274
- 'decrementProperty',
275
- 'get',
276
- 'getProperties',
277
- 'incrementProperty',
278
- 'notifyPropertyChange',
279
- 'removeObserver',
280
- 'set',
281
- 'setProperties',
282
- 'toggleProperty',
283
- ];
284
- EmberObjectMethods.forEach((method) => {
285
- PromiseManyArray.prototype[method] = function delegatedMethod(...args) {
286
- return Ember[method](this, ...args);
287
- };
288
- });
289
-
290
- const InheritedProxyMethods = [
291
- 'addArrayObserver',
292
- 'addObject',
293
- 'addObjects',
294
- 'any',
295
- 'arrayContentDidChange',
296
- 'arrayContentWillChange',
297
- 'clear',
298
- 'compact',
299
- 'every',
300
- 'filter',
301
- 'filterBy',
302
- 'find',
303
- 'findBy',
304
- 'getEach',
305
- 'includes',
306
- 'indexOf',
307
- 'insertAt',
308
- 'invoke',
309
- 'isAny',
310
- 'isEvery',
311
- 'lastIndexOf',
312
- 'map',
313
- 'mapBy',
314
- 'objectAt',
315
- 'objectsAt',
316
- 'popObject',
317
- 'pushObject',
318
- 'pushObjects',
319
- 'reduce',
320
- 'reject',
321
- 'rejectBy',
322
- 'removeArrayObserver',
323
- 'removeAt',
324
- 'removeObject',
325
- 'removeObjects',
326
- 'replace',
327
- 'reverseObjects',
328
- 'setEach',
329
- 'setObjects',
330
- 'shiftObject',
331
- 'slice',
332
- 'sortBy',
333
- 'toArray',
334
- 'uniq',
335
- 'uniqBy',
336
- 'unshiftObject',
337
- 'unshiftObjects',
338
- 'without',
339
- ];
340
- InheritedProxyMethods.forEach((method) => {
341
- PromiseManyArray.prototype[method] = function proxiedMethod(...args) {
342
- assert(`Cannot call ${method} before content is assigned.`, this.content);
343
- return this.content[method](...args);
344
- };
345
- });
306
+ if (DEPRECATE_PROMISE_MANY_ARRAY_BEHAVIORS) {
307
+ const EmberObjectMethods = [
308
+ 'addObserver',
309
+ 'cacheFor',
310
+ 'decrementProperty',
311
+ 'get',
312
+ 'getProperties',
313
+ 'incrementProperty',
314
+ 'notifyPropertyChange',
315
+ 'removeObserver',
316
+ 'set',
317
+ 'setProperties',
318
+ 'toggleProperty',
319
+ ];
320
+ EmberObjectMethods.forEach((method) => {
321
+ PromiseManyArray.prototype[method] = function delegatedMethod(...args) {
322
+ deprecate(
323
+ `The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
324
+ false,
325
+ {
326
+ id: 'ember-data:deprecate-promise-many-array-behaviors',
327
+ until: '5.0',
328
+ since: { enabled: '4.8', available: '4.8' },
329
+ for: 'ember-data',
330
+ }
331
+ );
332
+ return Ember[method](this, ...args);
333
+ };
334
+ });
335
+
336
+ const InheritedProxyMethods = [
337
+ 'addArrayObserver',
338
+ 'addObject',
339
+ 'addObjects',
340
+ 'any',
341
+ 'arrayContentDidChange',
342
+ 'arrayContentWillChange',
343
+ 'clear',
344
+ 'compact',
345
+ 'every',
346
+ 'filter',
347
+ 'filterBy',
348
+ 'find',
349
+ 'findBy',
350
+ 'getEach',
351
+ 'includes',
352
+ 'indexOf',
353
+ 'insertAt',
354
+ 'invoke',
355
+ 'isAny',
356
+ 'isEvery',
357
+ 'lastIndexOf',
358
+ 'map',
359
+ 'mapBy',
360
+ // TODO update RFC to note objectAt was deprecated (forEach was left for iteration)
361
+ 'objectAt',
362
+ 'objectsAt',
363
+ 'popObject',
364
+ 'pushObject',
365
+ 'pushObjects',
366
+ 'reduce',
367
+ 'reject',
368
+ 'rejectBy',
369
+ 'removeArrayObserver',
370
+ 'removeAt',
371
+ 'removeObject',
372
+ 'removeObjects',
373
+ 'replace',
374
+ 'reverseObjects',
375
+ 'setEach',
376
+ 'setObjects',
377
+ 'shiftObject',
378
+ 'slice',
379
+ 'sortBy',
380
+ 'toArray',
381
+ 'uniq',
382
+ 'uniqBy',
383
+ 'unshiftObject',
384
+ 'unshiftObjects',
385
+ 'without',
386
+ ];
387
+ InheritedProxyMethods.forEach((method) => {
388
+ PromiseManyArray.prototype[method] = function proxiedMethod(...args) {
389
+ deprecate(
390
+ `The ${method} method on ember-data's PromiseManyArray is deprecated. await the promise and work with the ManyArray directly.`,
391
+ false,
392
+ {
393
+ id: 'ember-data:deprecate-promise-many-array-behaviors',
394
+ until: '5.0',
395
+ since: { enabled: '4.8', available: '4.8' },
396
+ for: 'ember-data',
397
+ }
398
+ );
399
+ assert(`Cannot call ${method} before content is assigned.`, this.content);
400
+ return this.content[method](...args);
401
+ };
402
+ });
403
+ }
@@ -6,8 +6,8 @@ import { cached, tracked } from '@glimmer/tracking';
6
6
  import type Store from '@ember-data/store';
7
7
  import { storeFor } from '@ember-data/store';
8
8
  import { recordIdentifierFor } from '@ember-data/store/-private';
9
- import type { NotificationType } from '@ember-data/store/-private/record-notification-manager';
10
- import type RequestCache from '@ember-data/store/-private/request-cache';
9
+ import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
10
+ import type RequestCache from '@ember-data/store/-private/network/request-cache';
11
11
  import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
12
12
  import type { RecordData } from '@ember-data/types/q/record-data';
13
13
 
@@ -104,8 +104,9 @@ export function tagged(_target, key, desc) {
104
104
  }
105
105
 
106
106
  /**
107
- Historically InternalModel managed a state machine
108
- the currentState for which was reflected onto Model.
107
+ Historically EmberData managed a state machine
108
+ for each record, the localState for which
109
+ was reflected onto Model.
109
110
 
110
111
  This implements the flags and stateName for backwards compat
111
112
  with the state tree that used to be possible (listed below).
@@ -153,6 +154,7 @@ export default class RecordState {
153
154
  declare recordData: RecordData;
154
155
  declare _errorRequests: any[];
155
156
  declare _lastError: any;
157
+ declare handler: object;
156
158
 
157
159
  constructor(record: Model) {
158
160
  const store = storeFor(record)!;
@@ -232,27 +234,30 @@ export default class RecordState {
232
234
  }
233
235
  }
234
236
 
235
- notifications.subscribe(identity, (identifier: StableRecordIdentifier, type: NotificationType, key?: string) => {
236
- switch (type) {
237
- case 'state':
238
- this.notify('isNew');
239
- this.notify('isDeleted');
240
- this.notify('isDirty');
241
- break;
242
- case 'attributes':
243
- this.notify('isEmpty');
244
- this.notify('isDirty');
245
- break;
246
- case 'unload':
247
- this.notify('isNew');
248
- this.notify('isDeleted');
249
- break;
250
- case 'errors':
251
- this.updateInvalidErrors(this.record.errors);
252
- this.notify('isValid');
253
- break;
237
+ this.handler = notifications.subscribe(
238
+ identity,
239
+ (identifier: StableRecordIdentifier, type: NotificationType, key?: string) => {
240
+ switch (type) {
241
+ case 'state':
242
+ this.notify('isNew');
243
+ this.notify('isDeleted');
244
+ this.notify('isDirty');
245
+ break;
246
+ case 'attributes':
247
+ this.notify('isEmpty');
248
+ this.notify('isDirty');
249
+ break;
250
+ case 'errors':
251
+ this.updateInvalidErrors(this.record.errors);
252
+ this.notify('isValid');
253
+ break;
254
+ }
254
255
  }
255
- });
256
+ );
257
+ }
258
+
259
+ destroy() {
260
+ storeFor(this.record)!._notificationManager.unsubscribe(this.handler);
256
261
  }
257
262
 
258
263
  notify(key) {
@@ -304,7 +309,6 @@ export default class RecordState {
304
309
  return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0;
305
310
  }
306
311
 
307
- // TODO @runspired handle "unloadRecord" see note in InternalModel
308
312
  @tagged
309
313
  get isLoaded() {
310
314
  if (this.isNew) {
@@ -318,7 +322,7 @@ export default class RecordState {
318
322
  let rd = this.recordData;
319
323
  if (this.isDeleted) {
320
324
  assert(`Expected RecordData to implement isDeletionCommitted()`, rd.isDeletionCommitted);
321
- return rd.isDeletionCommitted();
325
+ return rd.isDeletionCommitted(this.identifier);
322
326
  }
323
327
  if (this.isNew || this.isEmpty || !this.isValid || this.isDirty || this.isLoading) {
324
328
  return false;
@@ -332,21 +336,21 @@ export default class RecordState {
332
336
  // TODO this is not actually an RFC'd concept. Determine the
333
337
  // correct heuristic to replace this with.
334
338
  assert(`Expected RecordData to implement isEmpty()`, rd.isEmpty);
335
- return !this.isNew && rd.isEmpty();
339
+ return !this.isNew && rd.isEmpty(this.identifier);
336
340
  }
337
341
 
338
342
  @tagged
339
343
  get isNew() {
340
344
  let rd = this.recordData;
341
345
  assert(`Expected RecordData to implement isNew()`, rd.isNew);
342
- return rd.isNew();
346
+ return rd.isNew(this.identifier);
343
347
  }
344
348
 
345
349
  @tagged
346
350
  get isDeleted() {
347
351
  let rd = this.recordData;
348
352
  assert(`Expected RecordData to implement isDeleted()`, rd.isDeleted);
349
- return rd.isDeleted();
353
+ return rd.isDeleted(this.identifier);
350
354
  }
351
355
 
352
356
  @tagged
@@ -357,12 +361,10 @@ export default class RecordState {
357
361
  @tagged
358
362
  get isDirty() {
359
363
  let rd = this.recordData;
360
- assert(`Expected RecordData to implement hasChangedAttributes()`, rd.hasChangedAttributes);
361
- assert(`Expected RecordData to implement isDeletionCommitted()`, rd.isDeletionCommitted);
362
- if (rd.isDeletionCommitted() || (this.isDeleted && this.isNew)) {
364
+ if (rd.isDeletionCommitted(this.identifier) || (this.isDeleted && this.isNew)) {
363
365
  return false;
364
366
  }
365
- return this.isNew || rd.hasChangedAttributes();
367
+ return this.isNew || rd.hasChangedAttrs(this.identifier);
366
368
  }
367
369
 
368
370
  @tagged