@ember-data/store 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/addon/-debug/index.js +35 -13
  2. package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
  3. package/addon/-private/caches/instance-cache.ts +690 -0
  4. package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
  5. package/addon/-private/index.ts +44 -24
  6. package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
  7. package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
  8. package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
  9. package/addon/-private/managers/record-array-manager.ts +377 -0
  10. package/addon/-private/managers/record-data-manager.ts +845 -0
  11. package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
  12. package/addon/-private/managers/record-notification-manager.ts +109 -0
  13. package/addon/-private/network/fetch-manager.ts +567 -0
  14. package/addon/-private/{finders.js → network/finders.js} +14 -17
  15. package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
  16. package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
  17. package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
  18. package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
  19. package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
  20. package/addon/-private/record-arrays/identifier-array.ts +924 -0
  21. package/addon/-private/{core-store.ts → store-service.ts} +574 -215
  22. package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
  23. package/addon/-private/{common.js → utils/common.js} +1 -2
  24. package/addon/-private/utils/construct-resource.ts +2 -2
  25. package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
  26. package/addon/-private/utils/is-non-empty-string.ts +1 -1
  27. package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
  28. package/addon/-private/utils/promise-record.ts +5 -6
  29. package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
  30. package/addon/-private/utils/uuid-polyfill.ts +73 -0
  31. package/package.json +12 -8
  32. package/addon/-private/backburner.js +0 -25
  33. package/addon/-private/errors-utils.js +0 -146
  34. package/addon/-private/fetch-manager.ts +0 -597
  35. package/addon/-private/identity-map.ts +0 -54
  36. package/addon/-private/instance-cache.ts +0 -387
  37. package/addon/-private/internal-model-factory.ts +0 -359
  38. package/addon/-private/internal-model-map.ts +0 -121
  39. package/addon/-private/model/internal-model.ts +0 -602
  40. package/addon/-private/record-array-manager.ts +0 -444
  41. package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
  42. package/addon/-private/record-arrays/record-array.ts +0 -318
  43. package/addon/-private/record-data-store-wrapper.ts +0 -243
  44. package/addon/-private/record-notification-manager.ts +0 -73
  45. package/addon/-private/weak-cache.ts +0 -125
@@ -0,0 +1,924 @@
1
+ /**
2
+ @module @ember-data/store
3
+ */
4
+ import { assert, deprecate } from '@ember/debug';
5
+ import { get, set } from '@ember/object';
6
+ import { dependentKeyCompat } from '@ember/object/compat';
7
+ import { compare } from '@ember/utils';
8
+ import { DEBUG } from '@glimmer/env';
9
+ import { tracked } from '@glimmer/tracking';
10
+ import Ember from 'ember';
11
+
12
+ import {
13
+ DEPRECATE_A_USAGE,
14
+ DEPRECATE_ARRAY_LIKE,
15
+ DEPRECATE_PROMISE_PROXIES,
16
+ DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS,
17
+ } from '@ember-data/private-build-infra/deprecations';
18
+ import { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
19
+ import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
20
+ import type { RecordInstance } from '@ember-data/types/q/record-instance';
21
+ import { Dict } from '@ember-data/types/q/utils';
22
+
23
+ import { recordIdentifierFor } from '../caches/instance-cache';
24
+ import type RecordArrayManager from '../managers/record-array-manager';
25
+ import { PromiseArray, promiseArray } from '../proxies/promise-proxies';
26
+ import type Store from '../store-service';
27
+
28
+ type KeyType = string | symbol | number;
29
+ const ARRAY_GETTER_METHODS = new Set<KeyType>([
30
+ Symbol.iterator,
31
+ 'concat',
32
+ 'entries',
33
+ 'every',
34
+ 'fill',
35
+ 'filter',
36
+ 'find',
37
+ 'findIndex',
38
+ 'flat',
39
+ 'flatMap',
40
+ 'forEach',
41
+ 'includes',
42
+ 'indexOf',
43
+ 'join',
44
+ 'keys',
45
+ 'lastIndexOf',
46
+ 'map',
47
+ 'reduce',
48
+ 'reduceRight',
49
+ 'slice',
50
+ 'some',
51
+ 'values',
52
+ ]);
53
+ const ARRAY_SETTER_METHODS = new Set<KeyType>(['push', 'pop', 'unshift', 'shift', 'splice', 'sort']);
54
+ const SYNC_PROPS = new Set<KeyType>(['[]', 'length', 'links', 'meta']);
55
+ function isArrayGetter(prop: KeyType): boolean {
56
+ return ARRAY_GETTER_METHODS.has(prop);
57
+ }
58
+ function isArraySetter(prop: KeyType): boolean {
59
+ return ARRAY_SETTER_METHODS.has(prop);
60
+ }
61
+
62
+ export const IDENTIFIER_ARRAY_TAG = Symbol('#tag');
63
+ export const SOURCE = Symbol('#source');
64
+ export const MUTATE = Symbol('#update');
65
+
66
+ function convertToInt(prop: KeyType): number | null {
67
+ if (typeof prop === 'symbol') return null;
68
+
69
+ const num = Number(prop);
70
+
71
+ if (isNaN(num)) return null;
72
+
73
+ return num % 1 === 0 ? num : null;
74
+ }
75
+
76
+ class Tag {
77
+ @tracked ref = null;
78
+ shouldReset: boolean = false;
79
+ }
80
+
81
+ type ProxiedMethod = (...args: unknown[]) => unknown;
82
+ declare global {
83
+ interface ProxyConstructor {
84
+ new <TSource extends object, TTarget extends object>(target: TSource, handler: ProxyHandler<TSource>): TTarget;
85
+ }
86
+ }
87
+
88
+ export type IdentifierArrayCreateOptions = {
89
+ identifiers: StableRecordIdentifier[];
90
+ type: string;
91
+ store: Store;
92
+ allowMutation: boolean;
93
+ manager: RecordArrayManager;
94
+ links?: Links | PaginationLinks | null;
95
+ meta?: Dict<unknown> | null;
96
+ };
97
+
98
+ function deprecateArrayLike(className: string, fnName: string, replName: string) {
99
+ deprecate(
100
+ `The \`${fnName}\` method on the class ${className} is deprecated. Use the native array method \`${replName}\` instead.`,
101
+ false,
102
+ {
103
+ id: 'ember-data:deprecate-array-like',
104
+ until: '5.0',
105
+ since: { enabled: '4.8', available: '4.8' },
106
+ for: 'ember-data',
107
+ }
108
+ );
109
+ }
110
+
111
+ interface PrivateState {
112
+ links: Links | PaginationLinks | null;
113
+ meta: Dict<unknown> | null;
114
+ }
115
+
116
+ /**
117
+ A record array is an array that contains records of a certain type (or modelName).
118
+ The record array materializes records as needed when they are retrieved for the first
119
+ time. You should not create record arrays yourself. Instead, an instance of
120
+ `RecordArray` or its subclasses will be returned by your application's store
121
+ in response to queries.
122
+
123
+ This class should not be imported and instantiated by consuming applications.
124
+
125
+ @class RecordArray
126
+ @public
127
+ */
128
+
129
+ interface IdentifierArray {
130
+ [MUTATE]?(prop: string, args: unknown[], result?: unknown): void;
131
+ }
132
+ interface IdentifierArray extends Array<RecordInstance> {}
133
+ class IdentifierArray {
134
+ declare DEPRECATED_CLASS_NAME: string;
135
+ /**
136
+ The flag to signal a `RecordArray` is currently loading data.
137
+ Example
138
+ ```javascript
139
+ let people = store.peekAll('person');
140
+ people.isUpdating; // false
141
+ people.update();
142
+ people.isUpdating; // true
143
+ ```
144
+ @property isUpdating
145
+ @public
146
+ @type Boolean
147
+ */
148
+ @tracked isUpdating: boolean = false;
149
+ isLoaded: boolean = true;
150
+ isDestroying: boolean = false;
151
+ isDestroyed: boolean = false;
152
+ _updatingPromise: PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> | null = null;
153
+
154
+ [IDENTIFIER_ARRAY_TAG] = new Tag();
155
+ [SOURCE]: StableRecordIdentifier[];
156
+
157
+ declare links: Links | PaginationLinks | null;
158
+ declare meta: Dict<unknown> | null;
159
+
160
+ /**
161
+ The modelClass represented by this record array.
162
+
163
+ @property type
164
+ @public
165
+ @deprecated
166
+ @type {subclass of Model}
167
+ */
168
+ declare modelName: string;
169
+ /**
170
+ The store that created this record array.
171
+
172
+ @property store
173
+ @private
174
+ @type Store
175
+ */
176
+ declare store: Store;
177
+ declare _manager: RecordArrayManager;
178
+
179
+ destroy() {
180
+ this.isDestroying = true;
181
+ // changing the reference breaks the Proxy
182
+ // this[SOURCE] = [];
183
+ this[SOURCE].length = 0;
184
+ this[IDENTIFIER_ARRAY_TAG].ref = null;
185
+ this.isDestroyed = true;
186
+ }
187
+
188
+ // length must be on self for proxied methods to work properly
189
+ @dependentKeyCompat
190
+ get length() {
191
+ return this[SOURCE].length;
192
+ }
193
+ set length(value) {
194
+ this[SOURCE].length = value;
195
+ }
196
+
197
+ constructor(options: IdentifierArrayCreateOptions) {
198
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
199
+ let self = this;
200
+ this.modelName = options.type;
201
+ this.store = options.store;
202
+ this._manager = options.manager;
203
+ this[SOURCE] = options.identifiers;
204
+ const store = options.store;
205
+ const boundFns = new Map<KeyType, ProxiedMethod>();
206
+ const _TAG = this[IDENTIFIER_ARRAY_TAG];
207
+ const PrivateState: PrivateState = {
208
+ links: options.links || null,
209
+ meta: options.meta || null,
210
+ };
211
+ let transaction: boolean = false;
212
+
213
+ // when a mutation occurs
214
+ // we track all mutations within the call
215
+ // and forward them as one
216
+
217
+ const proxy = new Proxy<StableRecordIdentifier[], RecordInstance[]>(this[SOURCE], {
218
+ get(target: StableRecordIdentifier[], prop: KeyType, receiver: IdentifierArray): unknown {
219
+ let index = convertToInt(prop);
220
+ if (_TAG.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
221
+ options.manager._syncArray(receiver as unknown as IdentifierArray);
222
+ _TAG.shouldReset = false;
223
+ }
224
+
225
+ if (index !== null) {
226
+ const identifier = target[index];
227
+ if (!transaction) {
228
+ _TAG.ref;
229
+ }
230
+ return identifier && store._instanceCache.getRecord(identifier);
231
+ }
232
+
233
+ if (prop === 'meta') return _TAG.ref, PrivateState.meta;
234
+ if (prop === 'links') return _TAG.ref, PrivateState.links;
235
+ if (prop === '[]') return _TAG.ref, receiver;
236
+
237
+ if (isArrayGetter(prop)) {
238
+ let fn = boundFns.get(prop);
239
+
240
+ if (fn === undefined) {
241
+ fn = function () {
242
+ _TAG.ref;
243
+ // array functions must run through Reflect to work properly
244
+ // binding via other means will not work.
245
+ transaction = true;
246
+ let result = Reflect.apply(target[prop] as ProxiedMethod, receiver, arguments) as unknown;
247
+ transaction = false;
248
+ return result;
249
+ };
250
+
251
+ boundFns.set(prop, fn);
252
+ }
253
+
254
+ return fn;
255
+ }
256
+
257
+ if (isArraySetter(prop)) {
258
+ let fn = boundFns.get(prop);
259
+
260
+ if (fn === undefined) {
261
+ fn = function () {
262
+ // array functions must run through Reflect to work properly
263
+ // binding via other means will not work.
264
+ if (!options.allowMutation) {
265
+ assert(`Mutating this array of records via ${String(prop)} is not allowed.`, options.allowMutation);
266
+ return;
267
+ }
268
+ const args: unknown[] = Array.prototype.slice.call(arguments);
269
+ assert(`Cannot start a new array transaction while a previous transaction is underway`, !transaction);
270
+ transaction = true;
271
+ let result = Reflect.apply(target[prop] as ProxiedMethod, receiver, args) as unknown;
272
+ self[MUTATE]!(prop as string, args, result);
273
+ _TAG.ref = null;
274
+ // TODO handle cache updates
275
+ transaction = false;
276
+ return result;
277
+ };
278
+
279
+ boundFns.set(prop, fn);
280
+ }
281
+
282
+ return fn;
283
+ }
284
+
285
+ if (prop in self) {
286
+ if (DEPRECATE_ARRAY_LIKE) {
287
+ if (prop === 'firstObject') {
288
+ deprecateArrayLike(self.DEPRECATED_CLASS_NAME, prop, '[0]');
289
+ return receiver.at(0);
290
+ } else if (prop === 'lastObject') {
291
+ deprecateArrayLike(self.DEPRECATED_CLASS_NAME, prop, 'at(-1)');
292
+ return receiver.at(-1);
293
+ }
294
+ }
295
+
296
+ let fn = boundFns.get(prop);
297
+ if (fn) return fn;
298
+
299
+ let outcome: unknown = self[prop];
300
+
301
+ if (typeof outcome === 'function') {
302
+ fn = function () {
303
+ _TAG.ref;
304
+ // array functions must run through Reflect to work properly
305
+ // binding via other means will not work.
306
+ return Reflect.apply(outcome as ProxiedMethod, receiver, arguments) as unknown;
307
+ };
308
+
309
+ boundFns.set(prop, fn);
310
+ return fn;
311
+ }
312
+
313
+ return _TAG.ref, outcome;
314
+ }
315
+
316
+ return target[prop];
317
+ },
318
+
319
+ set(target: StableRecordIdentifier[], prop: KeyType, value: unknown /*, receiver */): boolean {
320
+ if (prop === 'length') {
321
+ if (!transaction && value === 0) {
322
+ transaction = true;
323
+ _TAG.ref = null;
324
+ Reflect.set(target, prop, value);
325
+ self[MUTATE]!('length 0', []);
326
+ transaction = false;
327
+ return true;
328
+ } else if (transaction) {
329
+ return Reflect.set(target, prop, value);
330
+ } else {
331
+ assert(`unexpected length set`);
332
+ }
333
+ }
334
+ if (prop === 'links') {
335
+ PrivateState.links = (value || null) as PaginationLinks | Links | null;
336
+ return true;
337
+ }
338
+ if (prop === 'meta') {
339
+ PrivateState.meta = (value || null) as Dict<unknown> | null;
340
+ return true;
341
+ }
342
+ let index = convertToInt(prop);
343
+
344
+ if (index === null || index > target.length) {
345
+ if (prop in self) {
346
+ self[prop] = value;
347
+ return true;
348
+ }
349
+ return false;
350
+ }
351
+
352
+ if (!options.allowMutation) {
353
+ assert(`Mutating ${String(prop)} on this RecordArray is not allowed.`, options.allowMutation);
354
+ return false;
355
+ }
356
+
357
+ let original: StableRecordIdentifier | undefined = target[index];
358
+ let newIdentifier = extractIdentifierFromRecord(value as RecordInstance);
359
+ (target as unknown as Record<KeyType, unknown>)[index] = newIdentifier;
360
+ if (!transaction) {
361
+ self[MUTATE]!('replace cell', [index, original, newIdentifier]);
362
+ _TAG.ref = null;
363
+ }
364
+
365
+ return true;
366
+ },
367
+
368
+ deleteProperty(target: StableRecordIdentifier[], prop: string | symbol): boolean {
369
+ assert(`Deleting keys on managed arrays is disallowed`, transaction);
370
+ if (!transaction) {
371
+ return false;
372
+ }
373
+ return Reflect.deleteProperty(target, prop);
374
+ },
375
+
376
+ getPrototypeOf() {
377
+ return IdentifierArray.prototype;
378
+ },
379
+ }) as IdentifierArray;
380
+
381
+ if (DEPRECATE_A_USAGE) {
382
+ const meta = Ember.meta(this);
383
+ meta.hasMixin = (mixin: Object) => {
384
+ deprecate(`Do not call A() on EmberData RecordArrays`, false, {
385
+ id: 'ember-data:no-a-with-array-like',
386
+ until: '5.0',
387
+ since: { enabled: '4.8', available: '4.8' },
388
+ for: 'ember-data',
389
+ });
390
+ // @ts-expect-error ArrayMixin is more than a type
391
+ if (mixin === NativeArray || mixin === ArrayMixin) {
392
+ return true;
393
+ }
394
+ return false;
395
+ };
396
+ } else if (DEBUG) {
397
+ const meta = Ember.meta(this);
398
+ meta.hasMixin = (mixin: Object) => {
399
+ assert(`Do not call A() on EmberData RecordArrays`);
400
+ };
401
+ }
402
+
403
+ return proxy;
404
+ }
405
+
406
+ /**
407
+ Used to get the latest version of all of the records in this array
408
+ from the adapter.
409
+
410
+ Example
411
+
412
+ ```javascript
413
+ let people = store.peekAll('person');
414
+ people.isUpdating; // false
415
+
416
+ people.update().then(function() {
417
+ people.isUpdating; // false
418
+ });
419
+
420
+ people.isUpdating; // true
421
+ ```
422
+
423
+ @method update
424
+ @public
425
+ */
426
+ update(): PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> {
427
+ if (this.isUpdating) {
428
+ return this._updatingPromise!;
429
+ }
430
+
431
+ this.isUpdating = true;
432
+
433
+ let updatingPromise = this._update();
434
+ updatingPromise.finally(() => {
435
+ this._updatingPromise = null;
436
+ if (this.isDestroying || this.isDestroyed) {
437
+ return;
438
+ }
439
+ this.isUpdating = false;
440
+ });
441
+
442
+ this._updatingPromise = updatingPromise;
443
+
444
+ return updatingPromise;
445
+ }
446
+
447
+ /*
448
+ Update this RecordArray and return a promise which resolves once the update
449
+ is finished.
450
+ */
451
+ _update(): PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> {
452
+ return this.store.findAll(this.modelName, { reload: true });
453
+ }
454
+
455
+ // TODO deprecate
456
+ /**
457
+ Saves all of the records in the `RecordArray`.
458
+
459
+ Example
460
+
461
+ ```javascript
462
+ let messages = store.peekAll('message');
463
+ messages.forEach(function(message) {
464
+ message.hasBeenSeen = true;
465
+ });
466
+ messages.save();
467
+ ```
468
+
469
+ @method save
470
+ @public
471
+ @return {PromiseArray} promise
472
+ */
473
+ save(): PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> {
474
+ let promise = Promise.all(this.map((record) => this.store.saveRecord(record))).then(() => this);
475
+
476
+ if (DEPRECATE_PROMISE_PROXIES) {
477
+ return promiseArray<RecordInstance, IdentifierArray>(promise);
478
+ }
479
+
480
+ return promise;
481
+ }
482
+ }
483
+
484
+ export default IdentifierArray;
485
+
486
+ if (DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS) {
487
+ Object.defineProperty(IdentifierArray.prototype, 'type', {
488
+ get() {
489
+ deprecate(
490
+ `Using RecordArray.type to access the ModelClass for a record is deprecated. Use store.modelFor(<modelName>) instead.`,
491
+ false,
492
+ {
493
+ id: 'ember-data:deprecate-snapshot-model-class-access',
494
+ until: '5.0',
495
+ for: 'ember-data',
496
+ since: { available: '4.5.0', enabled: '4.5.0' },
497
+ }
498
+ );
499
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
500
+ if (!this.modelName) {
501
+ return null;
502
+ }
503
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
504
+ return this.store.modelFor(this.modelName);
505
+ },
506
+ });
507
+ }
508
+
509
+ export type CollectionCreateOptions = IdentifierArrayCreateOptions & {
510
+ query: Dict<unknown> | null;
511
+ isLoaded: boolean;
512
+ };
513
+ export class Collection extends IdentifierArray {
514
+ query: Dict<unknown> | null = null;
515
+
516
+ constructor(options: CollectionCreateOptions) {
517
+ super(options as IdentifierArrayCreateOptions);
518
+ this.query = options.query || null;
519
+ this.isLoaded = options.isLoaded || false;
520
+ }
521
+
522
+ _update(): PromiseArray<RecordInstance, Collection> | Promise<Collection> {
523
+ const { store, query } = this;
524
+
525
+ // TODO save options from initial request?
526
+ const promise = store.query(this.modelName, query, { _recordArray: this });
527
+
528
+ if (DEPRECATE_PROMISE_PROXIES) {
529
+ return promiseArray(promise);
530
+ }
531
+ return promise;
532
+ }
533
+
534
+ destroy() {
535
+ super.destroy();
536
+ this._manager._managed.delete(this);
537
+ this._manager._pending.delete(this);
538
+ }
539
+ }
540
+ // trick the proxy "in" check
541
+ Collection.prototype.query = null;
542
+
543
+ // Ensure instanceof works correctly
544
+ // Object.setPrototypeOf(IdentifierArray.prototype, Array.prototype);
545
+
546
+ if (DEPRECATE_ARRAY_LIKE) {
547
+ IdentifierArray.prototype.DEPRECATED_CLASS_NAME = 'RecordArray';
548
+ Collection.prototype.DEPRECATED_CLASS_NAME = 'RecordArray';
549
+ const EmberObjectMethods = [
550
+ 'addObserver',
551
+ 'cacheFor',
552
+ 'decrementProperty',
553
+ 'get',
554
+ 'getProperties',
555
+ 'incrementProperty',
556
+ 'notifyPropertyChange',
557
+ 'removeObserver',
558
+ 'set',
559
+ 'setProperties',
560
+ 'toggleProperty',
561
+ ];
562
+ EmberObjectMethods.forEach((method) => {
563
+ IdentifierArray.prototype[method] = function delegatedMethod(...args: unknown[]): unknown {
564
+ deprecate(
565
+ `The EmberObject ${method} method on the class ${this.DEPRECATED_CLASS_NAME} is deprecated. Use dot-notation javascript get/set access instead.`,
566
+ false,
567
+ {
568
+ id: 'ember-data:deprecate-array-like',
569
+ until: '5.0',
570
+ since: { enabled: '4.8', available: '4.8' },
571
+ for: 'ember-data',
572
+ }
573
+ );
574
+ return (Ember[method] as (...args: unknown[]) => unknown)(this, ...args);
575
+ };
576
+ });
577
+
578
+ IdentifierArray.prototype.addObject = function (obj: RecordInstance) {
579
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'addObject', 'push');
580
+ let index = this.indexOf(obj);
581
+ if (index === -1) {
582
+ this.push(obj);
583
+ }
584
+ return this;
585
+ };
586
+
587
+ IdentifierArray.prototype.addObjects = function (objs: RecordInstance[]) {
588
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'addObjects', 'push');
589
+ objs.forEach((obj: RecordInstance) => {
590
+ let index = this.indexOf(obj);
591
+ if (index === -1) {
592
+ this.push(obj);
593
+ }
594
+ });
595
+ return this;
596
+ };
597
+
598
+ IdentifierArray.prototype.popObject = function () {
599
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'popObject', 'pop');
600
+ return this.pop() as RecordInstance;
601
+ };
602
+
603
+ IdentifierArray.prototype.pushObject = function (obj: RecordInstance) {
604
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'pushObject', 'push');
605
+ this.push(obj);
606
+ return obj;
607
+ };
608
+
609
+ IdentifierArray.prototype.pushObjects = function (objs: RecordInstance[]) {
610
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'pushObjects', 'push');
611
+ this.push(...objs);
612
+ return this;
613
+ };
614
+
615
+ IdentifierArray.prototype.shiftObject = function () {
616
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'shiftObject', 'shift');
617
+ return this.shift()!;
618
+ };
619
+
620
+ IdentifierArray.prototype.unshiftObject = function (obj: RecordInstance) {
621
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'unshiftObject', 'unshift');
622
+ this.unshift(obj);
623
+ return obj;
624
+ };
625
+
626
+ IdentifierArray.prototype.unshiftObjects = function (objs: RecordInstance[]) {
627
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'unshiftObjects', 'unshift');
628
+ this.unshift(...objs);
629
+ return this;
630
+ };
631
+
632
+ IdentifierArray.prototype.objectAt = function (index: number) {
633
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'objectAt', 'at');
634
+ return this.at(index);
635
+ };
636
+
637
+ IdentifierArray.prototype.objectsAt = function (indeces: number[]) {
638
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'objectsAt', 'at');
639
+ return indeces.map((index) => this.at(index)!);
640
+ };
641
+
642
+ IdentifierArray.prototype.removeAt = function (index: number) {
643
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'removeAt', 'splice');
644
+ this.splice(index, 1);
645
+ return this;
646
+ };
647
+
648
+ IdentifierArray.prototype.insertAt = function (index: number, obj: RecordInstance) {
649
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'insertAt', 'splice');
650
+ this.splice(index, 0, obj);
651
+ return this;
652
+ };
653
+
654
+ IdentifierArray.prototype.removeObject = function (obj: RecordInstance) {
655
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'removeObject', 'splice');
656
+ this.splice(this.indexOf(obj), 1);
657
+ return this;
658
+ };
659
+
660
+ IdentifierArray.prototype.removeObjects = function (objs: RecordInstance[]) {
661
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'removeObjects', 'splice');
662
+ objs.forEach((obj) => this.splice(this.indexOf(obj), 1));
663
+ return this;
664
+ };
665
+
666
+ IdentifierArray.prototype.toArray = function () {
667
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'unshiftObjects', 'unshift');
668
+ return this.slice();
669
+ };
670
+
671
+ IdentifierArray.prototype.replace = function (idx: number, amt: number, objects?: RecordInstance[]) {
672
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'replace', 'splice');
673
+ if (objects) {
674
+ this.splice(idx, amt, ...objects);
675
+ } else {
676
+ this.splice(idx, amt);
677
+ }
678
+ };
679
+
680
+ IdentifierArray.prototype.clear = function () {
681
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'clear', 'length = 0');
682
+ this.splice(0, this.length);
683
+ return this;
684
+ };
685
+
686
+ IdentifierArray.prototype.setObjects = function (objects: RecordInstance[]) {
687
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'clear', 'length = 0');
688
+ assert(
689
+ `${this.DEPRECATED_CLASS_NAME}.setObjects expects to receive an array as its argument`,
690
+ Array.isArray(objects)
691
+ );
692
+ this.splice(0, this.length);
693
+ this.push(...objects);
694
+ return this;
695
+ };
696
+
697
+ IdentifierArray.prototype.reverseObjects = function () {
698
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'reverseObjects', 'reverse');
699
+ this.reverse();
700
+ return this;
701
+ };
702
+
703
+ IdentifierArray.prototype.compact = function () {
704
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'compact', 'filter');
705
+ return this.filter((v) => v !== null && v !== undefined);
706
+ };
707
+
708
+ IdentifierArray.prototype.any = function (callback, target) {
709
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'any', 'some');
710
+ return this.some(callback, target);
711
+ };
712
+
713
+ IdentifierArray.prototype.isAny = function (prop, value) {
714
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'isAny', 'some');
715
+ let hasValue = arguments.length === 2;
716
+ return this.some((v) => (hasValue ? v[prop] === value : v[prop] === true));
717
+ };
718
+
719
+ IdentifierArray.prototype.isEvery = function (prop, value) {
720
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'isEvery', 'every');
721
+ let hasValue = arguments.length === 2;
722
+ return this.every((v) => (hasValue ? v[prop] === value : v[prop] === true));
723
+ };
724
+
725
+ IdentifierArray.prototype.getEach = function (key: string) {
726
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'getEach', 'map');
727
+ return this.map((value) => get(value, key));
728
+ };
729
+
730
+ IdentifierArray.prototype.mapBy = function (key: string) {
731
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'mapBy', 'map');
732
+ return this.map((value) => get(value, key));
733
+ };
734
+
735
+ IdentifierArray.prototype.findBy = function (key: string, value?: unknown) {
736
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'findBy', 'find');
737
+ if (arguments.length === 2) {
738
+ return this.find((val) => {
739
+ return get(val, key) === value;
740
+ });
741
+ } else {
742
+ return this.find((val) => Boolean(get(val, key)));
743
+ }
744
+ };
745
+
746
+ IdentifierArray.prototype.filterBy = function (key: string, value?: unknown) {
747
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'filterBy', 'filter');
748
+ if (arguments.length === 2) {
749
+ return this.filter((value) => {
750
+ return Boolean(get(value, key));
751
+ });
752
+ }
753
+ return this.filter((value) => {
754
+ return Boolean(get(value, key));
755
+ });
756
+ };
757
+
758
+ IdentifierArray.prototype.sortBy = function (...sortKeys: string[]) {
759
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'sortBy', '.slice().sort');
760
+ return this.slice().sort((a, b) => {
761
+ for (let i = 0; i < sortKeys.length; i++) {
762
+ let key = sortKeys[i];
763
+ let propA = get(a, key);
764
+ let propB = get(b, key);
765
+ // return 1 or -1 else continue to the next sortKey
766
+ let compareValue = compare(propA, propB);
767
+
768
+ if (compareValue) {
769
+ return compareValue;
770
+ }
771
+ }
772
+ return 0;
773
+ });
774
+ };
775
+
776
+ // @ts-expect-error
777
+ IdentifierArray.prototype.invoke = function (key: string, ...args: unknown[]) {
778
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'invoke', 'forEach');
779
+ return this.map((value) => (value[key] as (...args: unknown[]) => unknown)(...args));
780
+ };
781
+
782
+ // @ts-expect-error
783
+ IdentifierArray.prototype.addArrayObserver = function () {
784
+ deprecateArrayLike(
785
+ this.DEPRECATED_CLASS_NAME,
786
+ 'addArrayObserver',
787
+ 'derived state or reacting at the change source'
788
+ );
789
+ };
790
+
791
+ // @ts-expect-error
792
+ IdentifierArray.prototype.removeArrayObserver = function () {
793
+ deprecateArrayLike(
794
+ this.DEPRECATED_CLASS_NAME,
795
+ 'removeArrayObserver',
796
+ 'derived state or reacting at the change source'
797
+ );
798
+ };
799
+
800
+ // @ts-expect-error
801
+ IdentifierArray.prototype.arrayContentWillChange = function () {
802
+ deprecateArrayLike(
803
+ this.DEPRECATED_CLASS_NAME,
804
+ 'arrayContentWillChange',
805
+ 'derived state or reacting at the change source'
806
+ );
807
+ };
808
+
809
+ // @ts-expect-error
810
+ IdentifierArray.prototype.arrayContentDidChange = function () {
811
+ deprecateArrayLike(
812
+ this.DEPRECATED_CLASS_NAME,
813
+ 'arrayContentDidChange',
814
+ 'derived state or reacting at the change source.'
815
+ );
816
+ };
817
+
818
+ // @ts-expect-error
819
+ IdentifierArray.prototype.reject = function (key: string, value?: unknown) {
820
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'reject', 'filter');
821
+ if (arguments.length === 2) {
822
+ return this.filter((value) => {
823
+ return !get(value, key);
824
+ });
825
+ }
826
+ return this.filter((value) => {
827
+ return !get(value, key);
828
+ });
829
+ };
830
+
831
+ IdentifierArray.prototype.rejectBy = function (key: string, value?: unknown) {
832
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'rejectBy', 'filter');
833
+ if (arguments.length === 2) {
834
+ return this.filter((value) => {
835
+ return !get(value, key);
836
+ });
837
+ }
838
+ return this.filter((value) => {
839
+ return !get(value, key);
840
+ });
841
+ };
842
+
843
+ IdentifierArray.prototype.setEach = function (key: string, value: unknown) {
844
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'setEach', 'forEach');
845
+ this.forEach((item) => set(item, key, value));
846
+ };
847
+
848
+ IdentifierArray.prototype.uniq = function () {
849
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'uniq', 'filter');
850
+ // all current managed arrays are already enforced as unique
851
+ return this.slice();
852
+ };
853
+
854
+ // @ts-expect-error
855
+ IdentifierArray.prototype.uniqBy = function (key: string) {
856
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'uniqBy', 'filter');
857
+ // all current managed arrays are already enforced as unique
858
+ let seen = new Set();
859
+ let result: RecordInstance[] = [];
860
+ this.forEach((item) => {
861
+ let value = get(item, key);
862
+ if (seen.has(value)) {
863
+ return;
864
+ }
865
+ seen.add(value);
866
+ result.push(item);
867
+ });
868
+ return result;
869
+ };
870
+
871
+ IdentifierArray.prototype.without = function (value: RecordInstance) {
872
+ deprecateArrayLike(this.DEPRECATED_CLASS_NAME, 'without', 'slice');
873
+ const newArr = this.slice();
874
+ const index = this.indexOf(value);
875
+ if (index !== -1) {
876
+ newArr.splice(index, 1);
877
+ }
878
+ return newArr;
879
+ };
880
+
881
+ // @ts-expect-error
882
+ IdentifierArray.prototype.firstObject = null;
883
+ // @ts-expect-error
884
+ IdentifierArray.prototype.lastObject = null;
885
+ }
886
+
887
+ type PromiseProxyRecord = { then(): void; content: RecordInstance | null | undefined };
888
+
889
+ function assertRecordPassedToHasMany(record: RecordInstance | PromiseProxyRecord) {
890
+ assert(
891
+ `All elements of a hasMany relationship must be instances of Model, you passed $${typeof record}`,
892
+ (function () {
893
+ try {
894
+ recordIdentifierFor(record);
895
+ return true;
896
+ } catch {
897
+ return false;
898
+ }
899
+ })()
900
+ );
901
+ }
902
+
903
+ function extractIdentifierFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
904
+ if (!recordOrPromiseRecord) {
905
+ return null;
906
+ }
907
+
908
+ if (isPromiseRecord(recordOrPromiseRecord)) {
909
+ let content = recordOrPromiseRecord.content;
910
+ assert(
911
+ 'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo relationship.',
912
+ content !== undefined && content !== null
913
+ );
914
+ assertRecordPassedToHasMany(content);
915
+ return recordIdentifierFor(content);
916
+ }
917
+
918
+ assertRecordPassedToHasMany(recordOrPromiseRecord);
919
+ return recordIdentifierFor(recordOrPromiseRecord);
920
+ }
921
+
922
+ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
923
+ return !!record.then;
924
+ }