ember-data-model-fragments 7.0.3 → 8.0.2

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,1131 +0,0 @@
1
- // eslint-disable-next-line ember/use-ember-data-rfc-395-imports
2
- import { RecordData } from 'ember-data/-private';
3
- import { diffArray } from '@ember-data/model/-private';
4
- import { recordDataFor } from '@ember-data/store/-private';
5
- import { assert } from '@ember/debug';
6
- import { typeOf } from '@ember/utils';
7
- import { isArray } from '@ember/array';
8
- import { getActualFragmentType, isFragment } from './fragment';
9
- import isInstanceOfType from './util/instance-of-type';
10
- import { gte } from 'ember-compatibility-helpers';
11
-
12
- class FragmentBehavior {
13
- constructor(recordData, definition) {
14
- this.recordData = recordData;
15
- this.definition = definition;
16
- }
17
-
18
- getDefaultValue(key) {
19
- const { options } = this.definition;
20
- if (options.defaultValue === undefined) {
21
- return null;
22
- }
23
- let defaultValue;
24
- if (typeof options.defaultValue === 'function') {
25
- const record = this.recordData._fragmentGetRecord();
26
- defaultValue = options.defaultValue.call(null, record, options, key);
27
- } else {
28
- defaultValue = options.defaultValue;
29
- }
30
- assert(
31
- "The fragment's default value must be an object or null",
32
- defaultValue === null ||
33
- typeOf(defaultValue) === 'object' ||
34
- isFragment(defaultValue),
35
- );
36
- if (defaultValue === null) {
37
- return null;
38
- }
39
- if (isFragment(defaultValue)) {
40
- assert(
41
- `The fragment's default value must be a '${this.definition.modelName}' fragment`,
42
- isInstanceOfType(
43
- defaultValue.store.modelFor(this.definition.modelName),
44
- defaultValue,
45
- ),
46
- );
47
- const recordData = recordDataFor(defaultValue);
48
- recordData.setFragmentOwner(this.recordData, key);
49
- return recordData;
50
- }
51
- return this.recordData._newFragmentRecordData(
52
- this.definition,
53
- defaultValue,
54
- );
55
- }
56
-
57
- pushData(fragment, canonical) {
58
- assert(
59
- 'Fragment value must be a RecordData',
60
- fragment === null || fragment instanceof RecordData,
61
- );
62
- assert(
63
- 'Fragment canonical value must be an object or null',
64
- canonical === null || typeOf(canonical) === 'object',
65
- );
66
-
67
- if (canonical === null) {
68
- // server replaced fragment with null
69
- return null;
70
- }
71
-
72
- if (fragment) {
73
- // merge the fragment with the new data from the server
74
- fragment._fragmentPushData({ attributes: canonical });
75
- return fragment;
76
- }
77
- return this.recordData._newFragmentRecordData(this.definition, canonical);
78
- }
79
-
80
- willCommit(fragment) {
81
- assert(
82
- 'Fragment value must be a RecordData',
83
- fragment instanceof RecordData,
84
- );
85
- fragment._fragmentWillCommit();
86
- }
87
-
88
- didCommit(fragment, canonical) {
89
- assert(
90
- 'Fragment value must be a RecordData',
91
- fragment === null || fragment instanceof RecordData,
92
- );
93
- assert(
94
- 'Fragment canonical value must be an object',
95
- canonical == null || typeOf(canonical) === 'object',
96
- );
97
-
98
- if (canonical == null) {
99
- fragment?._fragmentDidCommit(null);
100
-
101
- if (canonical === null) {
102
- // server replaced fragment with null
103
- return null;
104
- }
105
-
106
- // server confirmed in-flight fragment
107
- return fragment;
108
- }
109
-
110
- if (fragment) {
111
- // merge the fragment with the new data from the server
112
- fragment._fragmentDidCommit({ attributes: canonical });
113
- return fragment;
114
- }
115
-
116
- return this.recordData._newFragmentRecordData(this.definition, canonical);
117
- }
118
-
119
- commitWasRejected(fragment) {
120
- assert(
121
- 'Fragment value must be a RecordData',
122
- fragment instanceof RecordData,
123
- );
124
- fragment._fragmentCommitWasRejected();
125
- }
126
-
127
- rollback(fragment) {
128
- assert(
129
- 'Fragment value must be a RecordData',
130
- fragment instanceof RecordData,
131
- );
132
- fragment._fragmentRollbackAttributes();
133
- }
134
-
135
- unload(fragment) {
136
- assert(
137
- 'Fragment value must be a RecordData',
138
- fragment instanceof RecordData,
139
- );
140
- fragment._fragmentUnloadRecord();
141
- }
142
-
143
- isDirty(value, originalValue) {
144
- assert(
145
- 'Fragment value must be a RecordData',
146
- value === null || value instanceof RecordData,
147
- );
148
- assert(
149
- 'Fragment original value must be a RecordData',
150
- originalValue === null || originalValue instanceof RecordData,
151
- );
152
- return (
153
- value !== originalValue ||
154
- (value !== null && value.hasChangedAttributes())
155
- );
156
- }
157
-
158
- currentState(fragment) {
159
- assert(
160
- 'Fragment value must be a RecordData',
161
- fragment === null || fragment instanceof RecordData,
162
- );
163
- return fragment === null ? null : fragment.getCurrentState();
164
- }
165
-
166
- canonicalState(fragment) {
167
- assert(
168
- 'Fragment value must be a RecordData',
169
- fragment === null || fragment instanceof RecordData,
170
- );
171
- return fragment === null ? null : fragment.getCanonicalState();
172
- }
173
- }
174
-
175
- class FragmentArrayBehavior {
176
- constructor(recordData, definition) {
177
- this.recordData = recordData;
178
- this.definition = definition;
179
- }
180
-
181
- getDefaultValue(key) {
182
- const { options } = this.definition;
183
- if (options.defaultValue === undefined) {
184
- return [];
185
- }
186
- let defaultValue;
187
- if (typeof options.defaultValue === 'function') {
188
- const record = this.recordData._fragmentGetRecord();
189
- defaultValue = options.defaultValue.call(null, record, options, key);
190
- } else {
191
- defaultValue = options.defaultValue;
192
- }
193
- assert(
194
- "The fragment array's default value must be an array of fragments or null",
195
- defaultValue === null ||
196
- (isArray(defaultValue) &&
197
- defaultValue.every((v) => typeOf(v) === 'object' || isFragment(v))),
198
- );
199
- if (defaultValue === null) {
200
- return null;
201
- }
202
- return defaultValue.map((item) => {
203
- if (isFragment(item)) {
204
- assert(
205
- `The fragment array's default value can only include '${this.definition.modelName}' fragments`,
206
- isInstanceOfType(
207
- item.store.modelFor(this.definition.modelName),
208
- item,
209
- ),
210
- );
211
- const recordData = recordDataFor(defaultValue);
212
- recordData.setFragmentOwner(this.recordData, key);
213
- return recordData;
214
- }
215
- return this.recordData._newFragmentRecordData(this.definition, item);
216
- });
217
- }
218
-
219
- pushData(fragmentArray, canonical) {
220
- assert(
221
- 'Fragment array value must be an array of RecordData',
222
- fragmentArray === null ||
223
- (isArray(fragmentArray) &&
224
- fragmentArray.every((rd) => rd instanceof RecordData)),
225
- );
226
- assert(
227
- 'Fragment array canonical value must be an array of objects',
228
- canonical === null ||
229
- (isArray(canonical) && canonical.every((v) => typeOf(v) === 'object')),
230
- );
231
-
232
- if (canonical === null) {
233
- // push replaced fragment array with null
234
- return null;
235
- }
236
-
237
- // merge the fragment array with the pushed data
238
- return canonical.map((attributes, i) => {
239
- const fragment = fragmentArray?.[i];
240
- if (fragment) {
241
- fragment._fragmentPushData({ attributes });
242
- return fragment;
243
- } else {
244
- return this.recordData._newFragmentRecordData(
245
- this.definition,
246
- attributes,
247
- );
248
- }
249
- });
250
- }
251
-
252
- willCommit(fragmentArray) {
253
- assert(
254
- 'Fragment array value must be an array of RecordData',
255
- isArray(fragmentArray) &&
256
- fragmentArray.every((rd) => rd instanceof RecordData),
257
- );
258
- fragmentArray.forEach((fragment) => fragment._fragmentWillCommit());
259
- }
260
-
261
- didCommit(fragmentArray, canonical) {
262
- assert(
263
- 'Fragment array value must be an array of RecordData',
264
- fragmentArray === null ||
265
- (isArray(fragmentArray) &&
266
- fragmentArray.every((rd) => rd instanceof RecordData)),
267
- );
268
- assert(
269
- 'Fragment array canonical value must be an array of objects',
270
- canonical == null ||
271
- (isArray(canonical) && canonical.every((v) => typeOf(v) === 'object')),
272
- );
273
-
274
- if (canonical == null) {
275
- fragmentArray?.forEach((fragment) => fragment._fragmentDidCommit(null));
276
- if (canonical === null) {
277
- // server replaced fragment array with null
278
- return null;
279
- }
280
- // server confirmed in-flight fragments
281
- return fragmentArray;
282
- }
283
-
284
- // merge the fragment array with the new data from the server
285
- const result = canonical.map((attributes, i) => {
286
- const fragment = fragmentArray?.[i];
287
- if (fragment) {
288
- fragment._fragmentDidCommit({ attributes });
289
- return fragment;
290
- } else {
291
- return this.recordData._newFragmentRecordData(
292
- this.definition,
293
- attributes,
294
- );
295
- }
296
- });
297
-
298
- // cleanup the remaining fragments
299
- for (let i = canonical.length; i < fragmentArray?.length; ++i) {
300
- fragmentArray[i]._fragmentDidCommit(null);
301
- }
302
- return result;
303
- }
304
-
305
- commitWasRejected(fragmentArray) {
306
- assert(
307
- 'Fragment array value must be an array of RecordData',
308
- isArray(fragmentArray) &&
309
- fragmentArray.every((rd) => rd instanceof RecordData),
310
- );
311
- fragmentArray.forEach((fragment) => fragment._fragmentCommitWasRejected());
312
- }
313
-
314
- rollback(fragmentArray) {
315
- assert(
316
- 'Fragment array value must be an array of RecordData',
317
- isArray(fragmentArray) &&
318
- fragmentArray.every((rd) => rd instanceof RecordData),
319
- );
320
- fragmentArray.forEach((fragment) => fragment._fragmentRollbackAttributes());
321
- }
322
-
323
- unload(fragmentArray) {
324
- assert(
325
- 'Fragment array value must be an array of RecordData',
326
- isArray(fragmentArray) &&
327
- fragmentArray.every((rd) => rd instanceof RecordData),
328
- );
329
- fragmentArray.forEach((fragment) => fragment._fragmentUnloadRecord());
330
- }
331
-
332
- isDirty(value, originalValue) {
333
- assert(
334
- 'Fragment array value must be an array of RecordData',
335
- value === null ||
336
- (isArray(value) && value.every((rd) => rd instanceof RecordData)),
337
- );
338
- assert(
339
- 'Fragment array original value must be an array of RecordData',
340
- originalValue === null ||
341
- (isArray(originalValue) &&
342
- originalValue.every((rd) => rd instanceof RecordData)),
343
- );
344
- return (
345
- !isArrayEqual(value, originalValue) ||
346
- (value !== null && value.some((rd) => rd.hasChangedAttributes()))
347
- );
348
- }
349
-
350
- currentState(fragmentArray) {
351
- assert(
352
- 'Fragment array value must be an array of RecordData',
353
- fragmentArray === null ||
354
- (isArray(fragmentArray) &&
355
- fragmentArray.every((rd) => rd instanceof RecordData)),
356
- );
357
- return fragmentArray === null
358
- ? null
359
- : fragmentArray.map((fragment) => fragment.getCurrentState());
360
- }
361
-
362
- canonicalState(fragmentArray) {
363
- assert(
364
- 'Fragment array value must be an array of RecordData',
365
- fragmentArray === null ||
366
- (isArray(fragmentArray) &&
367
- fragmentArray.every((rd) => rd instanceof RecordData)),
368
- );
369
- return fragmentArray === null
370
- ? null
371
- : fragmentArray.map((fragment) => fragment.getCanonicalState());
372
- }
373
- }
374
-
375
- class ArrayBehavior {
376
- constructor(recordData, definition) {
377
- this.recordData = recordData;
378
- this.definition = definition;
379
- }
380
-
381
- getDefaultValue(key) {
382
- const { options } = this.definition;
383
- if (options.defaultValue === undefined) {
384
- return [];
385
- }
386
- let defaultValue;
387
- if (typeof options.defaultValue === 'function') {
388
- const record = this.recordData._fragmentGetRecord();
389
- defaultValue = options.defaultValue.call(null, record, options, key);
390
- } else {
391
- defaultValue = options.defaultValue;
392
- }
393
- assert(
394
- "The array's default value must be an array or null",
395
- defaultValue === null || isArray(defaultValue),
396
- );
397
- if (defaultValue === null) {
398
- return null;
399
- }
400
- return defaultValue.slice();
401
- }
402
-
403
- pushData(array, canonical) {
404
- assert('Array value must be an array', array === null || isArray(array));
405
- assert(
406
- 'Array canonical value must be an array',
407
- canonical === null || isArray(canonical),
408
- );
409
- if (canonical === null) {
410
- return null;
411
- }
412
- return canonical.slice();
413
- }
414
-
415
- willCommit(array) {
416
- assert('Array value must be an array', isArray(array));
417
- // nothing to do
418
- }
419
-
420
- didCommit(array, canonical) {
421
- assert('Array value must be an array', array === null || isArray(array));
422
- assert(
423
- 'Array canonical value must be an array',
424
- canonical === null || canonical === undefined || isArray(canonical),
425
- );
426
- if (canonical === null) {
427
- // server replaced array with null
428
- return null;
429
- }
430
- if (canonical === undefined) {
431
- // server confirmed in-flight array
432
- return array;
433
- }
434
- // server returned new canonical data
435
- return canonical.slice();
436
- }
437
-
438
- commitWasRejected(array) {
439
- assert('Array value must be an array', isArray(array));
440
- // nothing to do
441
- }
442
-
443
- rollback(array) {
444
- assert('Array value must be an array', isArray(array));
445
- // nothing to do
446
- }
447
-
448
- unload(array) {
449
- assert('Array value must be an array', isArray(array));
450
- // nothing to do
451
- }
452
-
453
- isDirty(value, originalValue) {
454
- assert('Array value must be an array', value === null || isArray(value));
455
- assert(
456
- 'Array original value must be an array',
457
- originalValue === null || isArray(originalValue),
458
- );
459
- return !isArrayEqual(value, originalValue);
460
- }
461
-
462
- currentState(array) {
463
- assert('Array value must be an array', array === null || isArray(array));
464
- return array === null ? null : array.slice();
465
- }
466
-
467
- canonicalState(array) {
468
- assert('Array value must be an array', array === null || isArray(array));
469
- return array === null ? null : array.slice();
470
- }
471
- }
472
-
473
- export default class FragmentRecordData extends RecordData {
474
- constructor(identifier, storeWrapper) {
475
- super(identifier, storeWrapper);
476
-
477
- const behavior = Object.create(null);
478
- const definitions = this.storeWrapper.attributesDefinitionFor(
479
- this.modelName,
480
- );
481
- for (const [key, definition] of Object.entries(definitions)) {
482
- if (!definition.isFragment) {
483
- continue;
484
- }
485
- switch (definition.kind) {
486
- case 'fragment-array':
487
- behavior[key] = new FragmentArrayBehavior(this, definition);
488
- break;
489
- case 'fragment':
490
- behavior[key] = new FragmentBehavior(this, definition);
491
- break;
492
- case 'array':
493
- behavior[key] = new ArrayBehavior(this, definition);
494
- break;
495
- default:
496
- assert(`Unsupported fragment type: ${definition.kind}`);
497
- break;
498
- }
499
- }
500
- this._fragmentBehavior = behavior;
501
- }
502
-
503
- _getFragmentDefault(key) {
504
- const behavior = this._fragmentBehavior[key];
505
- assert(
506
- `Attribute '${key}' for model '${this.modelName}' must be a fragment`,
507
- behavior != null,
508
- );
509
- assert(
510
- 'Fragment default value was already initialized',
511
- !this.hasFragment(key),
512
- );
513
- const defaultValue = behavior.getDefaultValue(key);
514
- this._fragmentData[key] = defaultValue;
515
- return defaultValue;
516
- }
517
-
518
- getFragment(key) {
519
- if (key in this._fragments) {
520
- return this._fragments[key];
521
- } else if (key in this._inFlightFragments) {
522
- return this._inFlightFragments[key];
523
- } else if (key in this._fragmentData) {
524
- return this._fragmentData[key];
525
- } else {
526
- return this._getFragmentDefault(key);
527
- }
528
- }
529
-
530
- hasFragment(key) {
531
- return (
532
- key in this._fragments ||
533
- key in this._inFlightFragments ||
534
- key in this._fragmentData
535
- );
536
- }
537
-
538
- setDirtyFragment(key, value) {
539
- const behavior = this._fragmentBehavior[key];
540
- assert(
541
- `Attribute '${key}' for model '${this.modelName}' must be a fragment`,
542
- behavior != null,
543
- );
544
- let originalValue;
545
- if (key in this._inFlightFragments) {
546
- originalValue = this._inFlightFragments[key];
547
- } else if (key in this._fragmentData) {
548
- originalValue = this._fragmentData[key];
549
- } else {
550
- originalValue = this._getFragmentDefault(key);
551
- }
552
- const isDirty = behavior.isDirty(value, originalValue);
553
- const oldDirty = this.isFragmentDirty(key);
554
- if (isDirty !== oldDirty) {
555
- this.notifyStateChange(key);
556
- }
557
- if (isDirty) {
558
- this._fragments[key] = value;
559
- this.fragmentDidDirty();
560
- } else {
561
- delete this._fragments[key];
562
- this.fragmentDidReset();
563
- }
564
- // this._fragmentArrayCache[key]?.notify();
565
- }
566
-
567
- setDirtyAttribute(key, value) {
568
- assert(
569
- `Attribute '${key}' for model '${this.modelName}' must not be a fragment`,
570
- this._fragmentBehavior[key] == null,
571
- );
572
- const oldDirty = this.isAttrDirty(key);
573
- super.setDirtyAttribute(key, value);
574
-
575
- const isDirty = this.isAttrDirty(key);
576
- if (isDirty !== oldDirty) {
577
- this.notifyStateChange(key);
578
- }
579
- if (isDirty) {
580
- this.fragmentDidDirty();
581
- } else {
582
- this.fragmentDidReset();
583
- }
584
- }
585
-
586
- getFragmentOwner() {
587
- return this._fragmentOwner?.recordData;
588
- }
589
-
590
- setFragmentOwner(recordData, key) {
591
- assert(
592
- 'Fragment owner must be a RecordData',
593
- recordData instanceof RecordData,
594
- );
595
- assert(
596
- 'Fragment owner key must be a fragment',
597
- recordData._fragmentBehavior[key] != null,
598
- );
599
- assert(
600
- 'Fragments can only belong to one owner, try copying instead',
601
- !this._fragmentOwner || this._fragmentOwner.recordData === recordData,
602
- );
603
- this._fragmentOwner = { recordData, key };
604
- }
605
-
606
- _newFragmentRecordDataForKey(key, attributes) {
607
- const behavior = this._fragmentBehavior[key];
608
- assert(
609
- `Attribute '${key}' for model '${this.modelName}' must be a fragment`,
610
- behavior != null,
611
- );
612
- return this._newFragmentRecordData(behavior.definition, attributes);
613
- }
614
-
615
- _newFragmentRecordData(definition, attributes) {
616
- const type = getActualFragmentType(
617
- definition.modelName,
618
- definition.options,
619
- attributes,
620
- this._fragmentGetRecord(),
621
- );
622
- const recordData = this.storeWrapper.recordDataFor(type);
623
- recordData.setFragmentOwner(this, definition.name);
624
- recordData._fragmentPushData({ attributes });
625
- return recordData;
626
- }
627
-
628
- hasChangedAttributes() {
629
- return super.hasChangedAttributes() || this.hasChangedFragments();
630
- }
631
-
632
- hasChangedFragments() {
633
- return Object.keys(this._fragmentsOrInFlight).length > 0;
634
- }
635
-
636
- isFragmentDirty(key) {
637
- return this.__fragments?.[key] !== undefined;
638
- }
639
-
640
- getCanonicalState() {
641
- const result = Object.assign({}, this._data);
642
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
643
- const value =
644
- key in this._fragmentData
645
- ? this._fragmentData[key]
646
- : this._getFragmentDefault(key);
647
- result[key] = behavior.canonicalState(value);
648
- }
649
- return result;
650
- }
651
-
652
- getCurrentState() {
653
- const result = Object.assign(
654
- {},
655
- this._data,
656
- this._inFlightAttributes,
657
- this._attributes,
658
- );
659
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
660
- result[key] = behavior.currentState(this.getFragment(key));
661
- }
662
- return result;
663
- }
664
-
665
- /*
666
- Returns an object, whose keys are changed properties, and value is an
667
- [oldProp, newProp] array.
668
-
669
- @method changedAttributes
670
- @private
671
- */
672
- changedAttributes() {
673
- const result = super.changedAttributes();
674
- if (this.hasChangedFragments()) {
675
- Object.assign(result, this.changedFragments());
676
- }
677
- return result;
678
- }
679
-
680
- changedFragments() {
681
- const diffData = Object.create(null);
682
- for (const [key, newFragment] of Object.entries(
683
- this._fragmentsOrInFlight,
684
- )) {
685
- const behavior = this._fragmentBehavior[key];
686
- const oldFragment = this._fragmentData[key];
687
- diffData[key] = [
688
- behavior.canonicalState(oldFragment),
689
- behavior.currentState(newFragment),
690
- ];
691
- }
692
- return diffData;
693
- }
694
-
695
- _changedFragmentKeys(updates) {
696
- const changedKeys = [];
697
- const original = Object.assign(
698
- {},
699
- this._fragmentData,
700
- this._inFlightFragments,
701
- );
702
- for (const key of Object.keys(updates)) {
703
- if (this._fragments[key]) {
704
- continue;
705
- }
706
- const eitherIsNull = original[key] === null || updates[key] === null;
707
- if (
708
- eitherIsNull ||
709
- diffArray(original[key], updates[key]).firstChangeIndex !== null
710
- ) {
711
- changedKeys.push(key);
712
- }
713
- }
714
- return changedKeys;
715
- }
716
-
717
- pushData(data, calculateChange) {
718
- let changedFragmentKeys;
719
-
720
- const subFragmentsToProcess = [];
721
- if (data.attributes) {
722
- // copy so that we don't mutate the caller's data
723
- const attributes = Object.assign({}, data.attributes);
724
- data = Object.assign({}, data, { attributes });
725
-
726
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
727
- const canonical = data.attributes[key];
728
- if (canonical === undefined) {
729
- continue;
730
- }
731
- // strip fragments from the attributes so the super call does not process them
732
- delete data.attributes[key];
733
-
734
- subFragmentsToProcess.push({ key, behavior, canonical, attributes });
735
- }
736
- }
737
-
738
- // Wee need first the attributes to be setup before the fragment, to be able to access them (for polymorphic fragments for example)
739
- const changedAttributeKeys = super.pushData(data, calculateChange);
740
-
741
- if (data.attributes) {
742
- const newCanonicalFragments = {};
743
-
744
- subFragmentsToProcess.forEach(({ key, behavior, canonical }) => {
745
- const current =
746
- key in this._fragmentData
747
- ? this._fragmentData[key]
748
- : this._getFragmentDefault(key);
749
- newCanonicalFragments[key] = behavior.pushData(current, canonical);
750
- });
751
-
752
- if (calculateChange) {
753
- changedFragmentKeys = this._changedFragmentKeys(newCanonicalFragments);
754
- }
755
-
756
- Object.assign(this._fragmentData, newCanonicalFragments);
757
- // update fragment arrays
758
- changedFragmentKeys?.forEach((key) => {
759
- this._fragmentArrayCache[key]?.notify();
760
- });
761
- }
762
-
763
- const changedKeys = mergeArrays(changedAttributeKeys, changedFragmentKeys);
764
- if (gte('ember-data', '4.5.0') && changedKeys?.length > 0) {
765
- internalModelFor(this).notifyAttributes(changedKeys);
766
- }
767
- // on ember-data 2.8 - 4.4, InternalModel.setupData will notify
768
- return changedKeys || [];
769
- }
770
-
771
- willCommit() {
772
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
773
- const data = this.getFragment(key);
774
- if (data) {
775
- behavior.willCommit(data);
776
- }
777
- }
778
- this._inFlightFragments = this._fragments;
779
- this._fragments = null;
780
- // this.notifyStateChange();
781
- super.willCommit();
782
- }
783
-
784
- /**
785
- * Checks if the fragments which are considered as changed are still
786
- * different to the state which is acknowledged by the server.
787
- *
788
- * This method is needed when data for the internal model is pushed and the
789
- * pushed data might acknowledge dirty attributes as confirmed.
790
- */
791
- _updateChangedFragments() {
792
- for (const key of Object.keys(this._fragments)) {
793
- const value = this._fragments[key];
794
- const originalValue = this._fragmentData[key];
795
- const behavior = this._fragmentBehavior[key];
796
- const isDirty = behavior.isDirty(value, originalValue);
797
- if (!isDirty) {
798
- delete this._fragments[key];
799
- }
800
- }
801
- }
802
-
803
- didCommit(data) {
804
- if (data?.attributes) {
805
- // copy so that we don't mutate the caller's data
806
- const attributes = Object.assign({}, data.attributes);
807
- data = Object.assign({}, data, { attributes });
808
- }
809
-
810
- const newCanonicalFragments = {};
811
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
812
- let canonical;
813
- if (data?.attributes) {
814
- // strip fragments from the attributes so the super call does not process them
815
- canonical = data.attributes[key];
816
- delete data.attributes[key];
817
- }
818
- const fragment =
819
- key in this._inFlightFragments
820
- ? this._inFlightFragments[key]
821
- : this._fragmentData[key];
822
- newCanonicalFragments[key] = behavior.didCommit(fragment, canonical);
823
- }
824
-
825
- const changedFragmentKeys = this._changedFragmentKeys(
826
- newCanonicalFragments,
827
- );
828
- Object.assign(this._fragmentData, newCanonicalFragments);
829
- this._inFlightFragments = null;
830
-
831
- this._updateChangedFragments();
832
-
833
- const changedAttributeKeys = super.didCommit(data);
834
-
835
- // update fragment arrays
836
- changedFragmentKeys.forEach((key) => {
837
- this._fragmentArrayCache[key]?.notify();
838
- });
839
-
840
- const changedKeys = mergeArrays(changedAttributeKeys, changedFragmentKeys);
841
- if (gte('ember-data', '4.5.0') && changedKeys?.length > 0) {
842
- internalModelFor(this).notifyAttributes(changedKeys);
843
- }
844
- // on ember-data 2.8 - 4.4, InternalModel.adapterDidCommit will notify
845
- return changedKeys;
846
- }
847
-
848
- commitWasRejected(identifier, errors) {
849
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
850
- const fragment =
851
- key in this._inFlightFragments
852
- ? this._inFlightFragments[key]
853
- : this._fragmentData[key];
854
- if (fragment == null) {
855
- continue;
856
- }
857
- behavior.commitWasRejected(fragment);
858
- }
859
- Object.assign(this._fragments, this._inFlightFragments);
860
- this._inFlightFragments = null;
861
- super.commitWasRejected(identifier, errors);
862
- }
863
-
864
- rollbackAttributes() {
865
- let dirtyFragmentKeys;
866
- if (this.hasChangedFragments()) {
867
- dirtyFragmentKeys = Object.keys(this._fragments);
868
- dirtyFragmentKeys.forEach((key) => {
869
- this.rollbackFragment(key);
870
- });
871
- this._fragments = null;
872
- }
873
- const dirtyAttributeKeys = super.rollbackAttributes();
874
- this.notifyStateChange();
875
- this.fragmentDidReset();
876
- return mergeArrays(dirtyAttributeKeys, dirtyFragmentKeys);
877
- }
878
-
879
- rollbackFragment(key) {
880
- const behavior = this._fragmentBehavior[key];
881
- assert(
882
- `Attribute '${key}' for model '${this.modelName}' must be a fragment`,
883
- behavior != null,
884
- );
885
- if (!this.isFragmentDirty(key)) {
886
- return;
887
- }
888
- delete this._fragments[key];
889
- const fragment = this._fragmentData[key];
890
- if (fragment == null) {
891
- return;
892
- }
893
- behavior.rollback(fragment);
894
- this._fragmentArrayCache[key]?.notify();
895
-
896
- if (!this.hasChangedAttributes()) {
897
- this.notifyStateChange(key);
898
- this.fragmentDidReset();
899
- }
900
- }
901
-
902
- reset() {
903
- super.reset();
904
- this.__fragments = null;
905
- this.__inFlightFragments = null;
906
- this.__fragmentData = null;
907
- this.__fragmentArrayCache = null;
908
- this._fragmentOwner = null;
909
- }
910
-
911
- unloadRecord() {
912
- for (const [key, behavior] of Object.entries(this._fragmentBehavior)) {
913
- const fragment = this._fragments[key];
914
- if (fragment != null) {
915
- behavior.unload(fragment);
916
- }
917
- const inFlight = this._inFlightFragments[key];
918
- if (inFlight != null) {
919
- behavior.unload(inFlight);
920
- }
921
- const fragmentData = this._fragmentData[key];
922
- if (fragmentData != null) {
923
- behavior.unload(fragmentData);
924
- }
925
- this._fragmentArrayCache[key]?.destroy();
926
- }
927
- super.unloadRecord();
928
- }
929
-
930
- /**
931
- * When a fragment becomes dirty, update the dirty state of the fragment's owner
932
- */
933
- fragmentDidDirty() {
934
- assert('Fragment is not dirty', this.hasChangedAttributes());
935
- if (!this._fragmentOwner) {
936
- return;
937
- }
938
- const { recordData: owner, key } = this._fragmentOwner;
939
- if (owner.isFragmentDirty(key)) {
940
- // fragment owner is already dirty
941
- return;
942
- }
943
- assert(
944
- `Fragment '${key}' in owner '${owner.modelName}' has not been initialized`,
945
- key in owner._fragmentData,
946
- );
947
-
948
- owner._fragments[key] = owner._fragmentData[key];
949
- owner.notifyStateChange(key);
950
- owner.fragmentDidDirty();
951
- }
952
-
953
- /**
954
- * When a fragment becomes clean, update the fragment owner
955
- */
956
- fragmentDidReset() {
957
- if (!this._fragmentOwner) {
958
- return;
959
- }
960
- const { recordData: owner, key } = this._fragmentOwner;
961
- if (!owner.isFragmentDirty(key)) {
962
- // fragment owner is already clean
963
- return;
964
- }
965
-
966
- const behavior = owner._fragmentBehavior[key];
967
- const value = owner.getFragment(key);
968
- const originalValue = owner._fragmentData[key];
969
- const isDirty = behavior.isDirty(value, originalValue);
970
-
971
- if (isDirty) {
972
- // fragment is still dirty
973
- return;
974
- }
975
-
976
- delete owner._fragments[key];
977
- owner.notifyStateChange(key);
978
- owner.fragmentDidReset();
979
- }
980
-
981
- notifyStateChange(key) {
982
- if (key && gte('ember-data', '4.5.0')) {
983
- this.storeWrapper.notifyPropertyChange(
984
- this.modelName,
985
- this.id,
986
- this.clientId,
987
- key,
988
- );
989
- } else {
990
- this.storeWrapper.notifyStateChange(
991
- this.modelName,
992
- this.id,
993
- this.clientId,
994
- key,
995
- );
996
- }
997
- }
998
-
999
- /*
1000
- * Ensure that any changes to the fragment record-data also update the InternalModel's
1001
- * state machine and fire property change notifications on the Record
1002
- */
1003
-
1004
- _fragmentGetRecord(properties) {
1005
- if (gte('ember-data', '4.5.0')) {
1006
- return this.storeWrapper._store._instanceCache.getRecord(
1007
- this.identifier,
1008
- properties,
1009
- );
1010
- }
1011
- return internalModelFor(this).getRecord(properties);
1012
- }
1013
- _fragmentPushData(data) {
1014
- internalModelFor(this).setupData(data);
1015
- }
1016
- _fragmentWillCommit() {
1017
- internalModelFor(this).adapterWillCommit();
1018
- }
1019
- _fragmentDidCommit(data) {
1020
- internalModelFor(this).adapterDidCommit(data);
1021
- }
1022
- _fragmentRollbackAttributes() {
1023
- internalModelFor(this).rollbackAttributes();
1024
- }
1025
- _fragmentCommitWasRejected() {
1026
- internalModelFor(this).adapterDidInvalidate();
1027
- }
1028
- _fragmentUnloadRecord() {
1029
- internalModelFor(this).unloadRecord();
1030
- }
1031
-
1032
- /**
1033
- * The current dirty state
1034
- */
1035
- get _fragments() {
1036
- if (this.__fragments === null) {
1037
- this.__fragments = Object.create(null);
1038
- }
1039
- return this.__fragments;
1040
- }
1041
-
1042
- set _fragments(v) {
1043
- this.__fragments = v;
1044
- }
1045
-
1046
- /**
1047
- * The current saved state
1048
- */
1049
- get _fragmentData() {
1050
- if (this.__fragmentData === null) {
1051
- this.__fragmentData = Object.create(null);
1052
- }
1053
- return this.__fragmentData;
1054
- }
1055
-
1056
- set _fragmentData(v) {
1057
- this.__fragmentData = v;
1058
- }
1059
-
1060
- /**
1061
- * The fragments which are currently being saved to the backend
1062
- */
1063
- get _inFlightFragments() {
1064
- if (this.__inFlightFragments === null) {
1065
- this.__inFlightFragments = Object.create(null);
1066
- }
1067
- return this.__inFlightFragments;
1068
- }
1069
-
1070
- set _inFlightFragments(v) {
1071
- this.__inFlightFragments = v;
1072
- }
1073
-
1074
- get _fragmentsOrInFlight() {
1075
- return this.__inFlightFragments &&
1076
- Object.keys(this.__inFlightFragments).length > 0
1077
- ? this.__inFlightFragments
1078
- : this.__fragments || {};
1079
- }
1080
-
1081
- /**
1082
- * Fragment array instances
1083
- *
1084
- * This likely belongs in InternalModel, since ember-data caches its has-many
1085
- * arrays in `InternalModel#_manyArrayCache`. But we can't extend InternalModel.
1086
- */
1087
- get _fragmentArrayCache() {
1088
- if (this.__fragmentArrayCache === null) {
1089
- this.__fragmentArrayCache = Object.create(null);
1090
- }
1091
- return this.__fragmentArrayCache;
1092
- }
1093
- }
1094
-
1095
- function internalModelFor(recordData) {
1096
- const store = recordData.storeWrapper._store;
1097
- if (gte('ember-data', '4.5.0')) {
1098
- return store._instanceCache._internalModelForResource(
1099
- recordData.identifier,
1100
- );
1101
- }
1102
- return store._internalModelForResource(recordData.identifier);
1103
- }
1104
-
1105
- function isArrayEqual(a, b) {
1106
- if (a === b) {
1107
- return true;
1108
- }
1109
- if (a === null || b === null) {
1110
- return false;
1111
- }
1112
- if (a.length !== b.length) {
1113
- return false;
1114
- }
1115
- for (let i = 0; i < a.length; ++i) {
1116
- if (a[i] !== b[i]) {
1117
- return false;
1118
- }
1119
- }
1120
- return true;
1121
- }
1122
-
1123
- function mergeArrays(a, b) {
1124
- if (b == null) {
1125
- return a;
1126
- }
1127
- if (a == null) {
1128
- return b;
1129
- }
1130
- return [...a, ...b];
1131
- }