@ember-data/store 4.4.0-alpha.9 → 4.4.1

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 (33) hide show
  1. package/addon/-private/system/core-store.ts +147 -134
  2. package/addon/-private/system/ds-model-store.ts +10 -1
  3. package/addon/-private/system/fetch-manager.ts +21 -48
  4. package/addon/-private/system/model/internal-model.ts +192 -263
  5. package/addon/-private/system/model/states.js +41 -5
  6. package/addon/-private/system/{promise-proxies.ts → promise-proxies.js} +21 -31
  7. package/addon/-private/system/{record-array-manager.ts → record-array-manager.js} +60 -87
  8. package/addon/-private/system/record-arrays/adapter-populated-record-array.js +95 -0
  9. package/addon/-private/system/record-arrays/{record-array.ts → record-array.js} +75 -96
  10. package/addon/-private/system/record-data-for.ts +0 -2
  11. package/addon/-private/system/references/belongs-to.ts +2 -3
  12. package/addon/-private/system/references/has-many.ts +2 -4
  13. package/addon/-private/system/schema-definition-service.ts +2 -2
  14. package/addon/-private/system/snapshot-record-array.ts +11 -12
  15. package/addon/-private/system/snapshot.ts +7 -24
  16. package/addon/-private/system/store/common.js +1 -24
  17. package/addon/-private/system/store/finders.js +5 -53
  18. package/addon/-private/system/store/internal-model-factory.ts +7 -8
  19. package/addon/-private/system/store/record-data-store-wrapper.ts +2 -7
  20. package/addon/-private/system/store/serializer-response.js +71 -0
  21. package/addon/-private/ts-interfaces/ds-model.ts +7 -15
  22. package/addon/-private/ts-interfaces/ember-data-json-api.ts +0 -3
  23. package/addon/-private/ts-interfaces/minimum-adapter-interface.ts +20 -19
  24. package/addon/-private/ts-interfaces/minimum-serializer-interface.ts +6 -27
  25. package/addon/-private/ts-interfaces/record-data.ts +1 -4
  26. package/addon/-private/ts-interfaces/record-instance.ts +1 -3
  27. package/addon/-private/ts-interfaces/store.ts +0 -1
  28. package/addon/-private/utils/promise-record.ts +3 -3
  29. package/index.js +0 -3
  30. package/package.json +6 -7
  31. package/addon/-private/system/promise-proxy-base.js +0 -7
  32. package/addon/-private/system/record-arrays/adapter-populated-record-array.ts +0 -129
  33. package/addon/-private/system/store/serializer-response.ts +0 -85
@@ -5,19 +5,8 @@ import { get } from '@ember/object';
5
5
  import { _backburner as emberBackburner, cancel, run } from '@ember/runloop';
6
6
  import { DEBUG } from '@glimmer/env';
7
7
 
8
- import { importSync } from '@embroider/macros';
9
- import RSVP, { resolve } from 'rsvp';
8
+ import RSVP, { Promise } from 'rsvp';
10
9
 
11
- import type { ManyArray } from '@ember-data/model/-private';
12
- import RecordState from '@ember-data/model/-private/record-state';
13
- import type { ManyArrayCreateArgs } from '@ember-data/model/-private/system/many-array';
14
- import type {
15
- BelongsToProxyCreateArgs,
16
- BelongsToProxyMeta,
17
- } from '@ember-data/model/-private/system/promise-belongs-to';
18
- import type PromiseBelongsTo from '@ember-data/model/-private/system/promise-belongs-to';
19
- import type { HasManyProxyCreateArgs } from '@ember-data/model/-private/system/promise-many-array';
20
- import type PromiseManyArray from '@ember-data/model/-private/system/promise-many-array';
21
10
  import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra';
22
11
  import type {
23
12
  BelongsToRelationship,
@@ -25,21 +14,16 @@ import type {
25
14
  RecordData as DefaultRecordData,
26
15
  } from '@ember-data/record-data/-private';
27
16
  import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition';
28
- import type {
29
- DefaultSingleResourceRelationship,
30
- RelationshipRecordData,
31
- } from '@ember-data/record-data/-private/ts-interfaces/relationship-record-data';
32
17
 
33
- import type { DSModel } from '../../ts-interfaces/ds-model';
18
+ import { DSModel } from '../../ts-interfaces/ds-model';
34
19
  import type { StableRecordIdentifier } from '../../ts-interfaces/identifier';
35
- import type { ChangedAttributesHash, RecordData } from '../../ts-interfaces/record-data';
20
+ import type { RecordData } from '../../ts-interfaces/record-data';
36
21
  import type { JsonApiResource, JsonApiValidationError } from '../../ts-interfaces/record-data-json-api';
37
- import type { RelationshipSchema } from '../../ts-interfaces/record-data-schemas';
38
22
  import type { RecordInstance } from '../../ts-interfaces/record-instance';
39
23
  import type { FindOptions } from '../../ts-interfaces/store';
40
- import type { Dict } from '../../ts-interfaces/utils';
24
+ import type { ConfidentDict } from '../../ts-interfaces/utils';
41
25
  import type CoreStore from '../core-store';
42
- import type { CreateRecordProperties } from '../core-store';
26
+ import type Store from '../ds-model-store';
43
27
  import { errorsHashToArray } from '../errors-utils';
44
28
  import recordDataFor from '../record-data-for';
45
29
  import { BelongsToReference, HasManyReference, RecordReference } from '../references';
@@ -47,11 +31,10 @@ import Snapshot from '../snapshot';
47
31
  import { internalModelFactoryFor } from '../store/internal-model-factory';
48
32
  import RootState from './states';
49
33
 
50
- type PrivateModelModule = {
51
- ManyArray: { create(args: ManyArrayCreateArgs): ManyArray };
52
- PromiseBelongsTo: { create(args: BelongsToProxyCreateArgs): PromiseBelongsTo };
53
- PromiseManyArray: new (...args: unknown[]) => PromiseManyArray;
54
- };
34
+ // move to TS hacks module that we can delete when this is no longer a necessary recast
35
+ type ManyArray = InstanceType<typeof import('@ember-data/model/-private').ManyArray>;
36
+ type PromiseBelongsTo = InstanceType<typeof import('@ember-data/model/-private').PromiseBelongsTo>;
37
+ type PromiseManyArray = InstanceType<typeof import('@ember-data/model/-private').PromiseManyArray>;
55
38
 
56
39
  /**
57
40
  @module @ember-data/store
@@ -59,22 +42,18 @@ type PrivateModelModule = {
59
42
 
60
43
  const { hasOwnProperty } = Object.prototype;
61
44
 
62
- let _ManyArray: PrivateModelModule['ManyArray'];
63
- let _PromiseBelongsTo: PrivateModelModule['PromiseBelongsTo'];
64
- let _PromiseManyArray: PrivateModelModule['PromiseManyArray'];
45
+ let ManyArray: ManyArray;
46
+ let PromiseBelongsTo: PromiseBelongsTo;
47
+ let _PromiseManyArray: any; // TODO find a way to get the klass type here
65
48
 
66
49
  let _found = false;
67
50
  let _getModelPackage: () => boolean;
68
51
  if (HAS_MODEL_PACKAGE) {
69
52
  _getModelPackage = function () {
70
53
  if (!_found) {
71
- let modelPackage = importSync('@ember-data/model/-private') as PrivateModelModule;
72
- ({
73
- ManyArray: _ManyArray,
74
- PromiseBelongsTo: _PromiseBelongsTo,
75
- PromiseManyArray: _PromiseManyArray,
76
- } = modelPackage);
77
- if (_ManyArray && _PromiseBelongsTo && _PromiseManyArray) {
54
+ let modelPackage = require('@ember-data/model/-private');
55
+ ({ ManyArray, PromiseBelongsTo, PromiseManyArray: _PromiseManyArray } = modelPackage);
56
+ if (ManyArray && PromiseBelongsTo && _PromiseManyArray) {
78
57
  _found = true;
79
58
  }
80
59
  }
@@ -82,6 +61,13 @@ if (HAS_MODEL_PACKAGE) {
82
61
  };
83
62
  }
84
63
 
64
+ interface BelongsToMetaWrapper {
65
+ key: string;
66
+ store: CoreStore;
67
+ originatingInternalModel: InternalModel;
68
+ modelName: string;
69
+ }
70
+
85
71
  /*
86
72
  The TransitionChainMap caches the `state.enters`, `state.setups`, and final state reached
87
73
  when transitioning from one state to another, so that future transitions can replay the
@@ -92,32 +78,18 @@ if (HAS_MODEL_PACKAGE) {
92
78
  and setups. It may also be faster to do a two level cache (from: { to }) instead of caching based
93
79
  on a key that adds the two together.
94
80
  */
95
- // TODO before deleting the state machine we should
96
- // ensure all things in this map were properly accounted for.
97
- // in the RecordState class.
98
81
  const TransitionChainMap = Object.create(null);
99
82
 
100
83
  const _extractPivotNameCache = Object.create(null);
101
84
  const _splitOnDotCache = Object.create(null);
102
85
 
103
- function splitOnDot(name: string): string[] {
86
+ function splitOnDot(name) {
104
87
  return _splitOnDotCache[name] || (_splitOnDotCache[name] = name.split('.'));
105
88
  }
106
89
 
107
- function extractPivotName(name: string): string {
90
+ function extractPivotName(name) {
108
91
  return _extractPivotNameCache[name] || (_extractPivotNameCache[name] = splitOnDot(name)[0]);
109
92
  }
110
-
111
- function isDSModel(record: RecordInstance | null): record is DSModel {
112
- return (
113
- HAS_MODEL_PACKAGE &&
114
- !!record &&
115
- 'constructor' in record &&
116
- 'isModel' in record.constructor &&
117
- record.constructor.isModel === true
118
- );
119
- }
120
-
121
93
  export default class InternalModel {
122
94
  declare _id: string | null;
123
95
  declare modelName: string;
@@ -134,28 +106,25 @@ export default class InternalModel {
134
106
 
135
107
  // Not typed yet
136
108
  declare _promiseProxy: any;
137
- declare _record: RecordInstance | null;
109
+ declare _record: any;
138
110
  declare _scheduledDestroy: any;
139
111
  declare _modelClass: any;
112
+ declare _deferredTriggers: any;
140
113
  declare __recordArrays: any;
141
114
  declare references: any;
142
115
  declare _recordReference: RecordReference;
143
- declare _manyArrayCache: Dict<ManyArray>;
116
+ declare _manyArrayCache: ConfidentDict<ManyArray>;
144
117
 
145
- declare _relationshipPromisesCache: Dict<Promise<ManyArray | RecordInstance>>;
146
- declare _relationshipProxyCache: Dict<PromiseManyArray | PromiseBelongsTo>;
118
+ declare _relationshipPromisesCache: ConfidentDict<RSVP.Promise<any>>;
119
+ declare _relationshipProxyCache: ConfidentDict<PromiseManyArray | PromiseBelongsTo>;
147
120
  declare error: any;
148
- declare currentState: RecordState;
121
+ declare currentState: any;
149
122
  declare _previousState: any;
150
- declare store: CoreStore;
151
- declare identifier: StableRecordIdentifier;
152
123
 
153
- constructor(store: CoreStore, identifier: StableRecordIdentifier) {
124
+ constructor(public store: CoreStore | Store, public identifier: StableRecordIdentifier) {
154
125
  if (HAS_MODEL_PACKAGE) {
155
126
  _getModelPackage();
156
127
  }
157
- this.store = store;
158
- this.identifier = identifier;
159
128
  this._id = identifier.id;
160
129
  this._isUpdatingId = false;
161
130
  this.modelName = identifier.type;
@@ -194,6 +163,7 @@ export default class InternalModel {
194
163
  this._relationshipPromisesCache = Object.create(null);
195
164
  this._relationshipProxyCache = Object.create(null);
196
165
  this.references = Object.create(null);
166
+ this._deferredTriggers = [];
197
167
  this.currentState = RootState.empty;
198
168
  }
199
169
 
@@ -286,27 +256,15 @@ export default class InternalModel {
286
256
  }
287
257
  }
288
258
 
289
- getRecord(properties?: CreateRecordProperties): RecordInstance {
290
- let record = this._record;
291
-
292
- if (this._isDematerializing) {
293
- // TODO we should assert here instead of this return.
294
- return null as unknown as RecordInstance;
295
- }
296
-
297
- if (!record) {
259
+ getRecord(properties?): Object {
260
+ if (!this._record && !this._isDematerializing) {
298
261
  let { store } = this;
299
262
 
300
- record = this._record = store._instantiateRecord(
301
- this,
302
- this.modelName,
303
- this._recordData,
304
- this.identifier,
305
- properties
306
- );
263
+ this._record = store._instantiateRecord(this, this.modelName, this._recordData, this.identifier, properties);
264
+ this._triggerDeferredTriggers();
307
265
  }
308
266
 
309
- return record;
267
+ return this._record;
310
268
  }
311
269
 
312
270
  dematerializeRecord() {
@@ -330,11 +288,9 @@ export default class InternalModel {
330
288
  });
331
289
 
332
290
  if (this._record) {
333
- let keys = Object.keys(this._relationshipProxyCache);
334
- keys.forEach((key) => {
335
- let proxy = this._relationshipProxyCache[key]!;
336
- if (proxy.destroy) {
337
- proxy.destroy();
291
+ Object.keys(this._relationshipProxyCache).forEach((key) => {
292
+ if (this._relationshipProxyCache[key].destroy) {
293
+ this._relationshipProxyCache[key].destroy();
338
294
  }
339
295
  delete this._relationshipProxyCache[key];
340
296
  });
@@ -359,6 +315,7 @@ export default class InternalModel {
359
315
  // destroyRecord follows up deleteRecord with save(). This prevents an unecessary save for a new record
360
316
  this._deletedRecordWasNew = true;
361
317
  this.send('deleteRecord');
318
+ this._triggerDeferredTriggers();
362
319
  this.unloadRecord();
363
320
  } else {
364
321
  this.send('deleteRecord');
@@ -367,9 +324,9 @@ export default class InternalModel {
367
324
  });
368
325
  }
369
326
 
370
- save(options: FindOptions = {}): Promise<void> {
327
+ save(options): Promise<void> {
371
328
  if (this._deletedRecordWasNew) {
372
- return resolve();
329
+ return Promise.resolve();
373
330
  }
374
331
  let promiseLabel = 'DS: Model#save ' + this;
375
332
  let resolver = RSVP.defer<void>(promiseLabel);
@@ -378,8 +335,22 @@ export default class InternalModel {
378
335
  return this.store.scheduleSave(this, resolver, options) as Promise<void>;
379
336
  }
380
337
 
381
- reload(options: Dict<unknown> = {}): Promise<InternalModel> {
382
- return this.store._reloadRecord(this, options);
338
+ reload(options) {
339
+ if (!options) {
340
+ options = {};
341
+ }
342
+ let internalModel = this;
343
+
344
+ return internalModel.store._reloadRecord(internalModel, options).then(
345
+ function () {
346
+ //TODO NOW seems like we shouldn't need to do this
347
+ return internalModel;
348
+ },
349
+ function (error) {
350
+ throw error;
351
+ },
352
+ 'DS: Model#reload complete, update flags'
353
+ );
383
354
  }
384
355
 
385
356
  /*
@@ -454,32 +425,26 @@ export default class InternalModel {
454
425
  }
455
426
  }
456
427
 
457
- _findBelongsTo(
458
- key: string,
459
- resource: DefaultSingleResourceRelationship,
460
- relationshipMeta: RelationshipSchema,
461
- options?: Dict<unknown>
462
- ): Promise<RecordInstance | null> {
428
+ _findBelongsTo(key, resource, relationshipMeta, options) {
463
429
  // TODO @runspired follow up if parent isNew then we should not be attempting load here
464
- // TODO @runspired follow up on whether this should be in the relationship requests cache
465
430
  return this.store._findBelongsToByJsonApiResource(resource, this, relationshipMeta, options).then(
466
- (internalModel) => handleCompletedRelationshipRequest(this, key, resource._relationship, internalModel),
431
+ (internalModel) => handleCompletedRelationshipRequest(this, key, resource._relationship, internalModel, null),
467
432
  (e) => handleCompletedRelationshipRequest(this, key, resource._relationship, null, e)
468
433
  );
469
434
  }
470
435
 
471
- getBelongsTo(key: string, options?: Dict<unknown>): PromiseBelongsTo | RecordInstance | null {
436
+ getBelongsTo(key, options) {
472
437
  let resource = (this._recordData as DefaultRecordData).getBelongsTo(key);
473
438
  let identifier =
474
439
  resource && resource.data ? this.store.identifierCache.getOrCreateRecordIdentifier(resource.data) : null;
475
440
  let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
476
- assert(`Attempted to access a belongsTo relationship but no definition exists for it`, relationshipMeta);
441
+ if (!relationshipMeta) return;
477
442
 
478
443
  let store = this.store;
479
444
  let parentInternalModel = this;
480
445
  let async = relationshipMeta.options.async;
481
446
  let isAsync = typeof async === 'undefined' ? true : async;
482
- let _belongsToState: BelongsToProxyMeta = {
447
+ let _belongsToState: BelongsToMetaWrapper = {
483
448
  key,
484
449
  store,
485
450
  originatingInternalModel: this,
@@ -490,7 +455,7 @@ export default class InternalModel {
490
455
  let internalModel = identifier !== null ? store._internalModelForResource(identifier) : null;
491
456
 
492
457
  if (resource._relationship.state.hasFailedLoadAttempt) {
493
- return this._relationshipProxyCache[key] as PromiseBelongsTo;
458
+ return this._relationshipProxyCache[key];
494
459
  }
495
460
 
496
461
  let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
@@ -514,49 +479,49 @@ export default class InternalModel {
514
479
  "' with id " +
515
480
  parentInternalModel.id +
516
481
  ' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`belongsTo({ async: true })`)',
517
- toReturn === null || !internalModel.currentState.isEmpty
482
+ toReturn === null || !(toReturn as DSModel).isEmpty
518
483
  );
519
484
  return toReturn;
520
485
  }
521
486
  }
522
487
  }
523
488
 
524
- getManyArray(key: string, definition?: UpgradedMeta): ManyArray {
525
- assert('hasMany only works with the @ember-data/record-data package', HAS_RECORD_DATA_PACKAGE);
526
- let manyArray: ManyArray | undefined = this._manyArrayCache[key];
527
- if (!definition) {
528
- const graphFor = (
529
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
530
- ).graphFor;
531
- definition = graphFor(this.store).get(this.identifier, key).definition as UpgradedMeta;
532
- }
533
-
534
- if (!manyArray) {
535
- manyArray = _ManyArray.create({
536
- store: this.store,
537
- type: this.store.modelFor(definition.type),
538
- recordData: this._recordData as RelationshipRecordData,
539
- key,
540
- isPolymorphic: definition.isPolymorphic,
541
- isAsync: definition.isAsync,
542
- _inverseIsAsync: definition.inverseIsAsync,
543
- internalModel: this,
544
- isLoaded: !definition.isAsync,
545
- });
546
- this._manyArrayCache[key] = manyArray;
547
- }
489
+ getManyArray(key: string, definition?: UpgradedMeta) {
490
+ if (HAS_RECORD_DATA_PACKAGE) {
491
+ let manyArray = this._manyArrayCache[key];
492
+ if (!definition) {
493
+ const graphFor = require('@ember-data/record-data/-private').graphFor;
494
+ definition = graphFor(this.store).get(this.identifier, key).definition as UpgradedMeta;
495
+ }
496
+
497
+ if (!manyArray) {
498
+ manyArray = ManyArray.create({
499
+ store: this.store,
500
+ type: this.store.modelFor(definition.type),
501
+ recordData: this._recordData,
502
+ key,
503
+ isPolymorphic: definition.isPolymorphic,
504
+ isAsync: definition.isAsync,
505
+ _inverseIsAsync: definition.inverseIsAsync,
506
+ internalModel: this,
507
+ isLoaded: !definition.isAsync,
508
+ });
509
+ this._manyArrayCache[key] = manyArray;
510
+ }
548
511
 
549
- return manyArray;
512
+ return manyArray;
513
+ }
514
+ assert(`hasMany only works with the @ember-data/record-data package`, HAS_RECORD_DATA_PACKAGE);
550
515
  }
551
516
 
552
517
  fetchAsyncHasMany(
553
518
  key: string,
554
- relationship: ManyRelationship,
555
- manyArray: ManyArray,
556
- options?: Dict<unknown>
557
- ): Promise<ManyArray> {
519
+ relationship: ManyRelationship | BelongsToRelationship,
520
+ manyArray,
521
+ options
522
+ ): RSVP.Promise<unknown> {
558
523
  if (HAS_RECORD_DATA_PACKAGE) {
559
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<ManyArray> | undefined;
524
+ let loadingPromise = this._relationshipPromisesCache[key];
560
525
  if (loadingPromise) {
561
526
  return loadingPromise;
562
527
  }
@@ -564,27 +529,25 @@ export default class InternalModel {
564
529
  const jsonApi = this._recordData.getHasMany(key);
565
530
 
566
531
  loadingPromise = this.store._findHasManyByJsonApiResource(jsonApi, this, relationship, options).then(
567
- () => handleCompletedRelationshipRequest(this, key, relationship, manyArray),
532
+ () => handleCompletedRelationshipRequest(this, key, relationship, manyArray, null),
568
533
  (e) => handleCompletedRelationshipRequest(this, key, relationship, manyArray, e)
569
534
  );
570
535
  this._relationshipPromisesCache[key] = loadingPromise;
571
536
  return loadingPromise;
572
537
  }
573
- assert('hasMany only works with the @ember-data/record-data package');
538
+ assert(`hasMany only works with the @ember-data/record-data package`);
574
539
  }
575
540
 
576
- getHasMany(key: string, options?): PromiseManyArray | ManyArray {
541
+ getHasMany(key: string, options?) {
577
542
  if (HAS_RECORD_DATA_PACKAGE) {
578
- const graphFor = (
579
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
580
- ).graphFor;
581
- const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
543
+ const graphFor = require('@ember-data/record-data/-private').graphFor;
544
+ const relationship = graphFor(this.store).get(this.identifier, key);
582
545
  const { definition, state } = relationship;
583
546
  let manyArray = this.getManyArray(key, definition);
584
547
 
585
548
  if (definition.isAsync) {
586
549
  if (state.hasFailedLoadAttempt) {
587
- return this._relationshipProxyCache[key] as PromiseManyArray;
550
+ return this._relationshipProxyCache[key];
588
551
  }
589
552
 
590
553
  let promise = this.fetchAsyncHasMany(key, relationship, manyArray, options);
@@ -602,55 +565,47 @@ export default class InternalModel {
602
565
  assert(`hasMany only works with the @ember-data/record-data package`);
603
566
  }
604
567
 
605
- _updatePromiseProxyFor(kind: 'hasMany', key: string, args: HasManyProxyCreateArgs): PromiseManyArray;
606
- _updatePromiseProxyFor(kind: 'belongsTo', key: string, args: BelongsToProxyCreateArgs): PromiseBelongsTo;
607
- _updatePromiseProxyFor(
608
- kind: 'belongsTo',
609
- key: string,
610
- args: { promise: Promise<RecordInstance | null> }
611
- ): PromiseBelongsTo;
612
568
  _updatePromiseProxyFor(
613
569
  kind: 'hasMany' | 'belongsTo',
614
570
  key: string,
615
- args: BelongsToProxyCreateArgs | HasManyProxyCreateArgs | { promise: Promise<RecordInstance | null> }
616
- ): PromiseBelongsTo | PromiseManyArray {
571
+ args: {
572
+ promise: RSVP.Promise<any>;
573
+ content?: RecordInstance | ManyArray | null;
574
+ _belongsToState?: BelongsToMetaWrapper;
575
+ }
576
+ ) {
617
577
  let promiseProxy = this._relationshipProxyCache[key];
618
578
  if (kind === 'hasMany') {
619
- const { promise, content } = args as HasManyProxyCreateArgs;
620
579
  if (promiseProxy) {
621
- assert(`Expected a PromiseManyArray`, '_update' in promiseProxy);
622
- promiseProxy._update(promise, content);
580
+ promiseProxy._update(args.promise, args.content);
623
581
  } else {
624
- promiseProxy = this._relationshipProxyCache[key] = new _PromiseManyArray(promise, content);
582
+ promiseProxy = this._relationshipProxyCache[key] = new _PromiseManyArray(args.promise, args.content);
625
583
  }
626
584
  return promiseProxy;
627
585
  }
628
586
  if (promiseProxy) {
629
- const { promise, content } = args as BelongsToProxyCreateArgs;
630
- assert(`Expected a PromiseBelongsTo`, '_belongsToState' in promiseProxy);
631
-
632
- if (content !== undefined) {
633
- promiseProxy.set('content', content);
587
+ if (args.content !== undefined) {
588
+ // this usage of `any` can be removed when `@types/ember_object` proxy allows `null` for content
589
+ promiseProxy.set('content', args.content as any);
634
590
  }
635
- promiseProxy.set('promise', promise);
591
+ promiseProxy.set('promise', args.promise);
636
592
  } else {
593
+ const klass = PromiseBelongsTo;
637
594
  // this usage of `any` can be removed when `@types/ember_object` proxy allows `null` for content
638
- this._relationshipProxyCache[key] = promiseProxy = _PromiseBelongsTo.create(args as any);
595
+ this._relationshipProxyCache[key] = klass.create(args as any);
639
596
  }
640
597
 
641
- return promiseProxy;
598
+ return this._relationshipProxyCache[key];
642
599
  }
643
600
 
644
- reloadHasMany(key: string, options) {
601
+ reloadHasMany(key, options) {
645
602
  if (HAS_RECORD_DATA_PACKAGE) {
646
603
  let loadingPromise = this._relationshipPromisesCache[key];
647
604
  if (loadingPromise) {
648
605
  return loadingPromise;
649
606
  }
650
- const graphFor = (
651
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
652
- ).graphFor;
653
- const relationship = graphFor(this.store).get(this.identifier, key) as ManyRelationship;
607
+ const graphFor = require('@ember-data/record-data/-private').graphFor;
608
+ const relationship = graphFor(this.store).get(this.identifier, key);
654
609
  const { definition, state } = relationship;
655
610
 
656
611
  state.hasFailedLoadAttempt = false;
@@ -667,8 +622,8 @@ export default class InternalModel {
667
622
  assert(`hasMany only works with the @ember-data/record-data package`);
668
623
  }
669
624
 
670
- reloadBelongsTo(key: string, options?: Dict<unknown>): Promise<RecordInstance | null> {
671
- let loadingPromise = this._relationshipPromisesCache[key] as Promise<RecordInstance | null> | undefined;
625
+ reloadBelongsTo(key, options) {
626
+ let loadingPromise = this._relationshipPromisesCache[key];
672
627
  if (loadingPromise) {
673
628
  return loadingPromise;
674
629
  }
@@ -680,7 +635,6 @@ export default class InternalModel {
680
635
  resource._relationship.state.shouldForceReload = true;
681
636
  }
682
637
  let relationshipMeta = this.store._relationshipMetaFor(this.modelName, null, key);
683
- assert(`Attempted to reload a belongsTo relationship but no definition exists for it`, relationshipMeta);
684
638
  let promise = this._findBelongsTo(key, resource, relationshipMeta, options);
685
639
  if (this._relationshipProxyCache[key]) {
686
640
  return this._updatePromiseProxyFor('belongsTo', key, { promise });
@@ -699,7 +653,7 @@ export default class InternalModel {
699
653
  destroy() {
700
654
  assert(
701
655
  'Cannot destroy an internalModel while its record is materialized',
702
- !this._record || this._record.isDestroyed || this._record.isDestroying
656
+ !this._record || this._record.get('isDestroyed') || this._record.get('isDestroying')
703
657
  );
704
658
  this.isDestroying = true;
705
659
  if (this._recordReference) {
@@ -708,13 +662,13 @@ export default class InternalModel {
708
662
  this._recordReference = null;
709
663
  let cache = this._manyArrayCache;
710
664
  Object.keys(cache).forEach((key) => {
711
- cache[key]!.destroy();
665
+ cache[key].destroy();
712
666
  delete cache[key];
713
667
  });
714
668
  if (this.references) {
715
669
  cache = this.references;
716
670
  Object.keys(cache).forEach((key) => {
717
- cache[key]!.destroy();
671
+ cache[key].destroy();
718
672
  delete cache[key];
719
673
  });
720
674
  }
@@ -724,35 +678,24 @@ export default class InternalModel {
724
678
  }
725
679
 
726
680
  setupData(data) {
727
- const hasRecord = this.hasRecord;
728
- if (hasRecord) {
729
- let changedKeys = this._recordData.pushData(data, true);
730
- this.notifyAttributes(changedKeys);
731
- } else {
732
- this._recordData.pushData(data);
681
+ let changedKeys = this._recordData.pushData(data, this.hasRecord);
682
+ if (this.hasRecord) {
683
+ // TODO @runspired should this be going through the notification manager?
684
+ this._record._notifyProperties(changedKeys);
733
685
  }
734
686
  this.send('pushedData');
735
687
  }
736
688
 
737
- notifyAttributes(keys: string[]): void {
738
- let manager = this.store._notificationManager;
739
- let { identifier } = this;
740
-
741
- for (let i = 0; i < keys.length; i++) {
742
- manager.notify(identifier, 'attributes', keys[i]);
743
- }
744
- }
745
-
746
- setDirtyHasMany(key: string, records) {
689
+ setDirtyHasMany(key, records) {
747
690
  assertRecordsPassedToHasMany(records);
748
691
  return this._recordData.setDirtyHasMany(key, extractRecordDatasFromRecords(records));
749
692
  }
750
693
 
751
- setDirtyBelongsTo(key: string, value) {
694
+ setDirtyBelongsTo(key, value) {
752
695
  return this._recordData.setDirtyBelongsTo(key, extractRecordDataFromRecord(value));
753
696
  }
754
697
 
755
- setDirtyAttribute<T>(key: string, value: T): T {
698
+ setDirtyAttribute(key, value) {
756
699
  if (this.isDeleted()) {
757
700
  if (DEBUG) {
758
701
  throw new EmberError(`Attempted to set '${key}' to '${value}' on the deleted record ${this}`);
@@ -774,11 +717,11 @@ export default class InternalModel {
774
717
  return value;
775
718
  }
776
719
 
777
- get isDestroyed(): boolean {
720
+ get isDestroyed() {
778
721
  return this._isDestroyed;
779
722
  }
780
723
 
781
- get hasRecord(): boolean {
724
+ get hasRecord() {
782
725
  return !!this._record;
783
726
  }
784
727
 
@@ -786,7 +729,7 @@ export default class InternalModel {
786
729
  return new Snapshot(options, this.identifier, this.store);
787
730
  }
788
731
 
789
- hasChangedAttributes(): boolean {
732
+ hasChangedAttributes() {
790
733
  if (!this.__recordData) {
791
734
  // no need to calculate changed attributes when calling `findRecord`
792
735
  return false;
@@ -794,7 +737,7 @@ export default class InternalModel {
794
737
  return this._recordData.hasChangedAttributes();
795
738
  }
796
739
 
797
- changedAttributes(): ChangedAttributesHash {
740
+ changedAttributes() {
798
741
  if (!this.__recordData) {
799
742
  // no need to calculate changed attributes when calling `findRecord`
800
743
  return {};
@@ -802,16 +745,16 @@ export default class InternalModel {
802
745
  return this._recordData.changedAttributes();
803
746
  }
804
747
 
805
- adapterWillCommit(): void {
748
+ adapterWillCommit() {
806
749
  this._recordData.willCommit();
807
750
  this.send('willCommit');
808
751
  }
809
752
 
810
- adapterDidDirty(): void {
753
+ adapterDidDirty() {
811
754
  this.send('becomeDirty');
812
755
  }
813
756
 
814
- send(name: string, context?) {
757
+ send(name, context?) {
815
758
  let currentState = this.currentState;
816
759
 
817
760
  if (!currentState[name]) {
@@ -842,7 +785,7 @@ export default class InternalModel {
842
785
  }
843
786
  }
844
787
 
845
- notifyPropertyChange(key: string) {
788
+ notifyPropertyChange(key) {
846
789
  if (this.hasRecord) {
847
790
  // TODO this should likely *mostly* be the `attributes` bucket
848
791
  // but it seems for local mutations we rely on computed updating
@@ -852,7 +795,7 @@ export default class InternalModel {
852
795
  }
853
796
  }
854
797
 
855
- notifyStateChange(key?: string) {
798
+ notifyStateChange(key?) {
856
799
  if (this.hasRecord) {
857
800
  this.store._notificationManager.notify(this.identifier, 'state');
858
801
  }
@@ -868,24 +811,24 @@ export default class InternalModel {
868
811
  rollbackAttributes() {
869
812
  this.store._backburner.join(() => {
870
813
  let dirtyKeys = this._recordData.rollbackAttributes();
871
- if (this.isError) {
814
+ if (get(this, 'isError')) {
872
815
  this.didCleanError();
873
816
  }
874
817
 
875
818
  this.send('rolledBack');
876
819
 
877
- if (this.hasRecord && dirtyKeys && dirtyKeys.length > 0) {
878
- this.notifyAttributes(dirtyKeys);
820
+ if (this._record && dirtyKeys && dirtyKeys.length > 0) {
821
+ this._record._notifyProperties(dirtyKeys);
879
822
  }
880
823
  });
881
824
  }
882
825
 
883
- transitionTo(name: string) {
826
+ transitionTo(name) {
884
827
  // POSSIBLE TODO: Remove this code and replace with
885
828
  // always having direct reference to state objects
886
829
 
887
830
  let pivotName = extractPivotName(name);
888
- let state: any = this.currentState;
831
+ let state = this.currentState;
889
832
  let transitionMapId = `${state.stateName}->${name}`;
890
833
 
891
834
  do {
@@ -930,12 +873,13 @@ export default class InternalModel {
930
873
  }
931
874
 
932
875
  this.currentState = state;
933
-
934
- // isDSModel is the guard we want, but may be too restrictive if
935
- // ember-m3 / ember-data-model-fragments were relying on this still.
936
- if (this.hasRecord && isDSModel(this._record)) {
937
- // TODO eliminate this.
876
+ if (this.hasRecord && typeof this._record.notifyPropertyChange === 'function') {
877
+ // TODO refactor Model to have all flags pull from the notification manager
878
+ // and for currentState.stateName to be constructed from flag state.
879
+ // Probably just port this work from ember-m3
880
+ // After that we can eliminate this.
938
881
  this.notifyStateChange('currentState');
882
+ // this._record.notifyPropertyChange('currentState');
939
883
  }
940
884
 
941
885
  for (i = 0, l = setups.length; i < l; i++) {
@@ -943,7 +887,7 @@ export default class InternalModel {
943
887
  }
944
888
  }
945
889
 
946
- _unhandledEvent(state, name: string, context) {
890
+ _unhandledEvent(state, name, context) {
947
891
  let errorMessage = 'Attempted to handle event `' + name + '` ';
948
892
  errorMessage += 'on ' + String(this) + ' while in state ';
949
893
  errorMessage += state.stateName + '. ';
@@ -955,6 +899,35 @@ export default class InternalModel {
955
899
  throw new EmberError(errorMessage);
956
900
  }
957
901
 
902
+ triggerLater(...args) {
903
+ if (this._deferredTriggers.push(args) !== 1) {
904
+ return;
905
+ }
906
+
907
+ this.store._updateInternalModel(this);
908
+ }
909
+
910
+ _triggerDeferredTriggers() {
911
+ //TODO: Before 1.0 we want to remove all the events that happen on the pre materialized record,
912
+ //but for now, we queue up all the events triggered before the record was materialized, and flush
913
+ //them once we have the record
914
+ if (!this.hasRecord) {
915
+ return;
916
+ }
917
+ let triggers = this._deferredTriggers;
918
+ let record = this._record;
919
+ let trigger = record.trigger;
920
+ // TODO Igor make nicer check
921
+ if (trigger && typeof trigger === 'function') {
922
+ for (let i = 0, l = triggers.length; i < l; i++) {
923
+ let eventName = triggers[i];
924
+ trigger.apply(record, eventName);
925
+ }
926
+ }
927
+
928
+ triggers.length = 0;
929
+ }
930
+
958
931
  removeFromInverseRelationships() {
959
932
  if (this.__recordData) {
960
933
  this.store._backburner.join(() => {
@@ -1084,34 +1057,26 @@ export default class InternalModel {
1084
1057
  this.store._notificationManager.notify(this.identifier, 'attributes');
1085
1058
  }
1086
1059
 
1087
- hasErrors(): boolean {
1088
- // TODO add assertion forcing consuming RecordData's to implement getErrors
1060
+ hasErrors() {
1089
1061
  if (this._recordData.getErrors) {
1090
1062
  return this._recordData.getErrors(this.identifier).length > 0;
1091
1063
  } else {
1092
- // we can't have errors if we never tried loading
1093
- if (!this._record) {
1094
- return false;
1095
- }
1096
- let errors = (this._record as DSModel).errors;
1064
+ let errors = (this.getRecord() as DSModel).errors;
1097
1065
  return errors.length > 0;
1098
1066
  }
1099
1067
  }
1100
1068
 
1101
1069
  // FOR USE DURING COMMIT PROCESS
1102
- adapterDidInvalidate(parsedErrors, error?) {
1070
+ adapterDidInvalidate(parsedErrors, error) {
1103
1071
  // TODO @runspired this should be handled by RecordState
1104
1072
  // and errors should be dirtied but lazily fetch if at
1105
1073
  // all possible. We should only notify errors here.
1106
1074
  let attribute;
1107
1075
  if (error && parsedErrors) {
1108
- // TODO add assertion forcing consuming RecordData's to implement getErrors
1109
1076
  if (!this._recordData.getErrors) {
1110
- let record = this.getRecord() as DSModel;
1111
- let errors = record.errors;
1112
1077
  for (attribute in parsedErrors) {
1113
1078
  if (hasOwnProperty.call(parsedErrors, attribute)) {
1114
- errors._add(attribute, parsedErrors[attribute]);
1079
+ (this.getRecord() as DSModel).errors._add(attribute, parsedErrors[attribute]);
1115
1080
  }
1116
1081
  }
1117
1082
  }
@@ -1152,9 +1117,7 @@ export default class InternalModel {
1152
1117
  // because of the intimate API access involved. This is something we will need to redesign.
1153
1118
  assert(`snapshot.belongsTo only supported for @ember-data/record-data`);
1154
1119
  }
1155
- const graphFor = (
1156
- importSync('@ember-data/record-data/-private') as typeof import('@ember-data/record-data/-private')
1157
- ).graphFor;
1120
+ const graphFor = require('@ember-data/record-data/-private').graphFor;
1158
1121
  const relationship = graphFor(this.store._storeWrapper).get(this.identifier, name);
1159
1122
 
1160
1123
  if (DEBUG && kind) {
@@ -1182,39 +1145,7 @@ export default class InternalModel {
1182
1145
  }
1183
1146
  }
1184
1147
 
1185
- function handleCompletedRelationshipRequest(
1186
- internalModel: InternalModel,
1187
- key: string,
1188
- relationship: BelongsToRelationship,
1189
- value: InternalModel | null
1190
- ): RecordInstance | null;
1191
- function handleCompletedRelationshipRequest(
1192
- internalModel: InternalModel,
1193
- key: string,
1194
- relationship: ManyRelationship,
1195
- value: ManyArray
1196
- ): ManyArray;
1197
- function handleCompletedRelationshipRequest(
1198
- internalModel: InternalModel,
1199
- key: string,
1200
- relationship: BelongsToRelationship,
1201
- value: null,
1202
- error: Error
1203
- ): never;
1204
- function handleCompletedRelationshipRequest(
1205
- internalModel: InternalModel,
1206
- key: string,
1207
- relationship: ManyRelationship,
1208
- value: ManyArray,
1209
- error: Error
1210
- ): never;
1211
- function handleCompletedRelationshipRequest(
1212
- internalModel: InternalModel,
1213
- key: string,
1214
- relationship: BelongsToRelationship | ManyRelationship,
1215
- value: ManyArray | InternalModel | null,
1216
- error?: Error
1217
- ): ManyArray | RecordInstance | null {
1148
+ function handleCompletedRelationshipRequest(internalModel, key, relationship, value, error) {
1218
1149
  delete internalModel._relationshipPromisesCache[key];
1219
1150
  relationship.state.shouldForceReload = false;
1220
1151
  const isHasMany = relationship.definition.kind === 'hasMany';
@@ -1222,7 +1153,7 @@ function handleCompletedRelationshipRequest(
1222
1153
  if (isHasMany) {
1223
1154
  // we don't notify the record property here to avoid refetch
1224
1155
  // only the many array
1225
- (value as ManyArray).notify();
1156
+ value.notify();
1226
1157
  }
1227
1158
 
1228
1159
  if (error) {
@@ -1237,9 +1168,7 @@ function handleCompletedRelationshipRequest(
1237
1168
  // has never been accessed
1238
1169
  if (proxy && !isHasMany) {
1239
1170
  if (proxy.content && proxy.content.isDestroying) {
1240
- // TODO @types/ember__object incorrectly disallows `null`, we should either
1241
- // override or fix upstream
1242
- (proxy as PromiseBelongsTo).set('content', null as unknown as undefined);
1171
+ proxy.set('content', null);
1243
1172
  }
1244
1173
  }
1245
1174
 
@@ -1247,14 +1176,14 @@ function handleCompletedRelationshipRequest(
1247
1176
  }
1248
1177
 
1249
1178
  if (isHasMany) {
1250
- (value as ManyArray).set('isLoaded', true);
1179
+ value.set('isLoaded', true);
1251
1180
  }
1252
1181
 
1253
1182
  relationship.state.hasFailedLoadAttempt = false;
1254
1183
  // only set to not stale if no error is thrown
1255
1184
  relationship.state.isStale = false;
1256
1185
 
1257
- return isHasMany || !value ? (value as ManyArray | null) : (value as InternalModel).getRecord();
1186
+ return value;
1258
1187
  }
1259
1188
 
1260
1189
  export function assertRecordsPassedToHasMany(records) {