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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 currentState 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,34 @@ 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 'unload':
251
+ this.notify('isNew');
252
+ this.notify('isDeleted');
253
+ break;
254
+ case 'errors':
255
+ this.updateInvalidErrors(this.record.errors);
256
+ this.notify('isValid');
257
+ break;
258
+ }
254
259
  }
255
- });
260
+ );
261
+ }
262
+
263
+ destroy() {
264
+ storeFor(this.record)!._notificationManager.unsubscribe(this.handler);
256
265
  }
257
266
 
258
267
  notify(key) {
@@ -304,7 +313,6 @@ export default class RecordState {
304
313
  return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0;
305
314
  }
306
315
 
307
- // TODO @runspired handle "unloadRecord" see note in InternalModel
308
316
  @tagged
309
317
  get isLoaded() {
310
318
  if (this.isNew) {
@@ -8,8 +8,8 @@ import type { BelongsToRelationship } from '@ember-data/record-data/-private';
8
8
  import type Store from '@ember-data/store';
9
9
  import { assertPolymorphicType } from '@ember-data/store/-debug';
10
10
  import { recordIdentifierFor } from '@ember-data/store/-private';
11
- import type { NotificationType } from '@ember-data/store/-private/record-notification-manager';
12
- import type { DebugWeakCache } from '@ember-data/store/-private/weak-cache';
11
+ import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
12
+ import type { DebugWeakCache } from '@ember-data/store/-private/utils/weak-cache';
13
13
  import type {
14
14
  LinkObject,
15
15
  Links,
@@ -125,7 +125,7 @@ export default class BelongsToReference {
125
125
  `type()` and `id()` methods form a composite key for the identity
126
126
  map. This can be used to access the id of an async relationship
127
127
  without triggering a fetch that would normally happen if you
128
- attempted to use `record.get('relationship.id')`.
128
+ attempted to use `record.relationship.id`.
129
129
 
130
130
  Example
131
131
 
@@ -277,7 +277,7 @@ export default class BelongsToReference {
277
277
  }
278
278
 
279
279
  _resource() {
280
- return this.store._instanceCache.recordDataFor(this.#identifier, false).getBelongsTo(this.key);
280
+ return this.store._instanceCache.getRecordData(this.#identifier).getBelongsTo(this.key);
281
281
  }
282
282
 
283
283
  /**
@@ -404,7 +404,7 @@ export default class BelongsToReference {
404
404
 
405
405
  /**
406
406
  `value()` synchronously returns the current value of the belongs-to
407
- relationship. Unlike `record.get('relationshipName')`, calling
407
+ relationship. Unlike `record.relationshipName`, calling
408
408
  `value()` on a reference does not trigger a fetch if the async
409
409
  relationship is not yet loaded. If the relationship is not loaded
410
410
  it will always return `null`.
@@ -11,8 +11,8 @@ import type { ManyRelationship } from '@ember-data/record-data/-private';
11
11
  import type Store from '@ember-data/store';
12
12
  import { recordIdentifierFor } from '@ember-data/store';
13
13
  import { assertPolymorphicType } from '@ember-data/store/-debug';
14
- import type { NotificationType } from '@ember-data/store/-private/record-notification-manager';
15
- import type { DebugWeakCache } from '@ember-data/store/-private/weak-cache';
14
+ import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
15
+ import type { DebugWeakCache } from '@ember-data/store/-private/utils/weak-cache';
16
16
  import type {
17
17
  CollectionResourceDocument,
18
18
  CollectionResourceRelationship,
@@ -131,7 +131,7 @@ export default class HasManyReference {
131
131
  }
132
132
 
133
133
  _resource() {
134
- return this.store._instanceCache.recordDataFor(this.#identifier, false).getHasMany(this.key);
134
+ return this.store._instanceCache.getRecordData(this.#identifier).getHasMany(this.key);
135
135
  }
136
136
 
137
137
  /**
@@ -435,16 +435,14 @@ export default class HasManyReference {
435
435
 
436
436
  let members = this.hasManyRelationship.currentState;
437
437
 
438
- //TODO @runspired determine isLoaded via a better means
439
438
  return members.every((identifier) => {
440
- let internalModel = this.store._instanceCache._internalModelForResource(identifier);
441
- return internalModel.isLoaded === true;
439
+ return this.store._instanceCache.recordIsLoaded(identifier, true) === true;
442
440
  });
443
441
  }
444
442
 
445
443
  /**
446
444
  `value()` synchronously returns the current value of the has-many
447
- relationship. Unlike `record.get('relationshipName')`, calling
445
+ relationship. Unlike `record.relationshipName`, calling
448
446
  `value()` on a reference does not trigger a fetch if the async
449
447
  relationship is not yet loaded. If the relationship is not loaded
450
448
  it will always return `null`.
@@ -474,7 +472,7 @@ export default class HasManyReference {
474
472
 
475
473
  let commentsRef = post.hasMany('comments');
476
474
 
477
- post.get('comments').then(function(comments) {
475
+ post.comments.then(function(comments) {
478
476
  commentsRef.value() === comments
479
477
  })
480
478
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ember-data/model",
3
- "version": "4.8.0-alpha.2",
3
+ "version": "4.8.0-alpha.3",
4
4
  "description": "The default blueprint for ember-cli addons.",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -18,9 +18,9 @@
18
18
  "test:node": "mocha"
19
19
  },
20
20
  "dependencies": {
21
- "@ember-data/canary-features": "4.8.0-alpha.2",
22
- "@ember-data/private-build-infra": "4.8.0-alpha.2",
23
- "@ember-data/store": "4.8.0-alpha.2",
21
+ "@ember-data/canary-features": "4.8.0-alpha.3",
22
+ "@ember-data/private-build-infra": "4.8.0-alpha.3",
23
+ "@ember-data/store": "4.8.0-alpha.3",
24
24
  "@ember/edition-utils": "^1.2.0",
25
25
  "@ember/string": "^3.0.0",
26
26
  "@embroider/macros": "^1.8.3",
@@ -34,7 +34,7 @@
34
34
  "inflection": "~1.13.2"
35
35
  },
36
36
  "devDependencies": {
37
- "@ember-data/unpublished-test-infra": "4.8.0-alpha.2",
37
+ "@ember-data/unpublished-test-infra": "4.8.0-alpha.3",
38
38
  "@ember/optional-features": "^2.0.0",
39
39
  "@ember/test-helpers": "~2.7.0",
40
40
  "broccoli-asset-rev": "^3.0.0",