@ember-data/store 5.4.0-beta.8 → 5.4.1-alpha.160

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 (65) hide show
  1. package/LICENSE.md +19 -7
  2. package/README.md +5 -8
  3. package/dist/-private.js +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/{cache-handler-C5ilAUZ5.js → many-array-BwVo-2vv.js} +1699 -870
  6. package/dist/many-array-BwVo-2vv.js.map +1 -0
  7. package/logos/NCC-1701-a-blue.svg +4 -0
  8. package/logos/NCC-1701-a-gold.svg +4 -0
  9. package/logos/NCC-1701-a-gold_100.svg +1 -0
  10. package/logos/NCC-1701-a-gold_base-64.txt +1 -0
  11. package/logos/NCC-1701-a.svg +4 -0
  12. package/logos/README.md +4 -0
  13. package/logos/docs-badge.svg +2 -0
  14. package/logos/github-header.svg +444 -0
  15. package/logos/social1.png +0 -0
  16. package/logos/social2.png +0 -0
  17. package/logos/warp-drive-logo-dark.svg +4 -0
  18. package/logos/warp-drive-logo-gold.svg +4 -0
  19. package/package.json +41 -49
  20. package/unstable-preview-types/-private/cache-handler/handler.d.ts +62 -0
  21. package/unstable-preview-types/-private/cache-handler/handler.d.ts.map +1 -0
  22. package/unstable-preview-types/-private/{cache-handler.d.ts → cache-handler/types.d.ts} +25 -62
  23. package/unstable-preview-types/-private/cache-handler/types.d.ts.map +1 -0
  24. package/unstable-preview-types/-private/cache-handler/utils.d.ts +34 -0
  25. package/unstable-preview-types/-private/cache-handler/utils.d.ts.map +1 -0
  26. package/unstable-preview-types/-private/caches/identifier-cache.d.ts +7 -3
  27. package/unstable-preview-types/-private/caches/identifier-cache.d.ts.map +1 -1
  28. package/unstable-preview-types/-private/caches/instance-cache.d.ts +4 -1
  29. package/unstable-preview-types/-private/caches/instance-cache.d.ts.map +1 -1
  30. package/unstable-preview-types/-private/debug/utils.d.ts +9 -0
  31. package/unstable-preview-types/-private/debug/utils.d.ts.map +1 -0
  32. package/unstable-preview-types/-private/document.d.ts +23 -14
  33. package/unstable-preview-types/-private/document.d.ts.map +1 -1
  34. package/unstable-preview-types/-private/legacy-model-support/record-reference.d.ts.map +1 -1
  35. package/unstable-preview-types/-private/legacy-model-support/shim-model-class.d.ts +3 -3
  36. package/unstable-preview-types/-private/legacy-model-support/shim-model-class.d.ts.map +1 -1
  37. package/unstable-preview-types/-private/managers/cache-capabilities-manager.d.ts +3 -3
  38. package/unstable-preview-types/-private/managers/cache-capabilities-manager.d.ts.map +1 -1
  39. package/unstable-preview-types/-private/managers/cache-manager.d.ts +23 -1
  40. package/unstable-preview-types/-private/managers/cache-manager.d.ts.map +1 -1
  41. package/unstable-preview-types/-private/managers/notification-manager.d.ts +11 -6
  42. package/unstable-preview-types/-private/managers/notification-manager.d.ts.map +1 -1
  43. package/unstable-preview-types/-private/managers/record-array-manager.d.ts +9 -5
  44. package/unstable-preview-types/-private/managers/record-array-manager.d.ts.map +1 -1
  45. package/unstable-preview-types/-private/record-arrays/identifier-array.d.ts +14 -2
  46. package/unstable-preview-types/-private/record-arrays/identifier-array.d.ts.map +1 -1
  47. package/unstable-preview-types/-private/record-arrays/many-array.d.ts +199 -0
  48. package/unstable-preview-types/-private/record-arrays/many-array.d.ts.map +1 -0
  49. package/unstable-preview-types/-private/record-arrays/native-proxy-type-fix.d.ts +3 -3
  50. package/unstable-preview-types/-private/store-service.d.ts +92 -19
  51. package/unstable-preview-types/-private/store-service.d.ts.map +1 -1
  52. package/unstable-preview-types/-private.d.ts +7 -4
  53. package/unstable-preview-types/-private.d.ts.map +1 -1
  54. package/unstable-preview-types/-types/q/cache-capabilities-manager.d.ts +4 -4
  55. package/unstable-preview-types/-types/q/cache-capabilities-manager.d.ts.map +1 -1
  56. package/unstable-preview-types/-types/q/ds-model.d.ts +3 -3
  57. package/unstable-preview-types/-types/q/ds-model.d.ts.map +1 -1
  58. package/unstable-preview-types/-types/q/schema-service.d.ts +16 -8
  59. package/unstable-preview-types/-types/q/schema-service.d.ts.map +1 -1
  60. package/unstable-preview-types/index.d.ts +31 -30
  61. package/unstable-preview-types/index.d.ts.map +1 -1
  62. package/dist/cache-handler-C5ilAUZ5.js.map +0 -1
  63. package/unstable-preview-types/-private/cache-handler.d.ts.map +0 -1
  64. /package/{ember-data-logo-dark.svg → logos/ember-data-logo-dark.svg} +0 -0
  65. /package/{ember-data-logo-light.svg → logos/ember-data-logo-light.svg} +0 -0
@@ -1,11 +1,12 @@
1
1
  import { deprecate, warn } from '@ember/debug';
2
2
  import { macroCondition, getGlobalConfig, dependencySatisfies, importSync } from '@embroider/macros';
3
- import { SkipCache, EnableHydration } from '@warp-drive/core-types/request';
4
- import { getOrSetGlobal, setTransient, peekTransient } from '@warp-drive/core-types/-private';
5
- import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_CLIENT_ORIGINATED, DEBUG_IDENTIFIER_BUCKET } from '@warp-drive/core-types/identifier';
6
- import { dasherize } from '@ember-data/request-utils/string';
7
- import { defineSignal, createSignal, subscribe, createArrayTags, addToTransaction, addTransactionCB } from '@ember-data/tracking/-private';
3
+ import { EnableHydration, SkipCache } from '@warp-drive/core-types/request';
4
+ import { setLogging, getRuntimeConfig } from '@warp-drive/core-types/runtime';
5
+ import { getOrSetGlobal, peekTransient, setTransient } from '@warp-drive/core-types/-private';
8
6
  import { _backburner } from '@ember/runloop';
7
+ import { defineSubscription, notifySignal, defineSignal, createSignal, subscribe, createArrayTags, addToTransaction, addTransactionCB } from '@ember-data/tracking/-private';
8
+ import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_IDENTIFIER_BUCKET, DEBUG_CLIENT_ORIGINATED } from '@warp-drive/core-types/identifier';
9
+ import { dasherize } from '@ember-data/request-utils/string';
9
10
  import { compat } from '@ember-data/tracking';
10
11
 
11
12
  /**
@@ -25,7 +26,7 @@ function coerceId(id) {
25
26
  until: '6.0',
26
27
  for: 'ember-data',
27
28
  since: {
28
- available: '5.3',
29
+ available: '4.13',
29
30
  enabled: '5.3'
30
31
  }
31
32
  });
@@ -60,7 +61,7 @@ function normalizeModelName(type) {
60
61
  until: '6.0',
61
62
  for: 'ember-data',
62
63
  since: {
63
- available: '5.3',
64
+ available: '4.13',
64
65
  enabled: '5.3'
65
66
  }
66
67
  });
@@ -75,7 +76,7 @@ function normalizeModelName(type) {
75
76
 
76
77
  function installPolyfill() {
77
78
  const isFastBoot = typeof FastBoot !== 'undefined';
78
- const CRYPTO = isFastBoot ? FastBoot.require('crypto') : window.crypto;
79
+ const CRYPTO = isFastBoot ? FastBoot.require('crypto') : globalThis.crypto;
79
80
  if (!CRYPTO.randomUUID) {
80
81
  // we might be able to optimize this by requesting more bytes than we need at a time
81
82
  const rng = function () {
@@ -129,16 +130,15 @@ function hasType(resource) {
129
130
  /**
130
131
  @module @ember-data/store
131
132
  */
132
- const IDENTIFIERS = getOrSetGlobal('IDENTIFIERS', new Set());
133
133
  const DOCUMENTS = getOrSetGlobal('DOCUMENTS', new Set());
134
134
  function isStableIdentifier(identifier) {
135
- return identifier[CACHE_OWNER] !== undefined || IDENTIFIERS.has(identifier);
135
+ return identifier[CACHE_OWNER] !== undefined;
136
136
  }
137
137
  function isDocumentIdentifier(identifier) {
138
138
  return DOCUMENTS.has(identifier);
139
139
  }
140
140
  const isFastBoot = typeof FastBoot !== 'undefined';
141
- const _crypto = isFastBoot ? FastBoot.require('crypto') : window.crypto;
141
+ const _crypto = isFastBoot ? FastBoot.require('crypto') : globalThis.crypto;
142
142
  if (macroCondition(getGlobalConfig().WarpDrive.polyfillUUID)) {
143
143
  installPolyfill();
144
144
  }
@@ -310,9 +310,11 @@ class IdentifierCache {
310
310
  */
311
311
 
312
312
  _getRecordIdentifier(resource, shouldGenerate) {
313
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
314
- // eslint-disable-next-line no-console
315
- console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
313
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
314
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
315
+ // eslint-disable-next-line no-console
316
+ console.groupCollapsed(`Identifiers: ${shouldGenerate ? 'Generating' : 'Peeking'} Identifier`, resource);
317
+ }
316
318
  }
317
319
  // short circuit if we're already the stable version
318
320
  if (isStableIdentifier(resource)) {
@@ -322,33 +324,41 @@ class IdentifierCache {
322
324
  throw new Error(`The supplied identifier ${JSON.stringify(resource)} does not belong to this store instance`);
323
325
  }
324
326
  }
325
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
326
- // eslint-disable-next-line no-console
327
- console.log(`Identifiers: cache HIT - Stable ${resource.lid}`);
328
- // eslint-disable-next-line no-console
329
- console.groupEnd();
327
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
328
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
329
+ // eslint-disable-next-line no-console
330
+ console.log(`Identifiers: cache HIT - Stable ${resource.lid}`);
331
+ // eslint-disable-next-line no-console
332
+ console.groupEnd();
333
+ }
330
334
  }
331
335
  return resource;
332
336
  }
333
337
 
334
338
  // the resource is unknown, ask the application to identify this data for us
335
339
  const lid = this._generate(resource, 'record');
336
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
337
- // eslint-disable-next-line no-console
338
- console.log(`Identifiers: ${lid ? 'no ' : ''}lid ${lid ? lid + ' ' : ''}determined for resource`, resource);
340
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
341
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
342
+ // eslint-disable-next-line no-console
343
+ console.log(`Identifiers: ${lid ? 'no ' : ''}lid ${lid ? lid + ' ' : ''}determined for resource`, resource);
344
+ }
339
345
  }
340
346
  let identifier = /*#__NOINLINE__*/getIdentifierFromLid(this._cache, lid, resource);
341
347
  if (identifier !== null) {
342
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
343
- // eslint-disable-next-line no-console
344
- console.groupEnd();
348
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
349
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
350
+ // eslint-disable-next-line no-console
351
+ console.groupEnd();
352
+ }
345
353
  }
346
354
  return identifier;
347
355
  }
348
356
  if (shouldGenerate === 0) {
349
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
350
- // eslint-disable-next-line no-console
351
- console.groupEnd();
357
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
358
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
359
+ // eslint-disable-next-line no-console
360
+ console.groupEnd();
361
+ }
352
362
  }
353
363
  return;
354
364
  }
@@ -366,9 +376,11 @@ class IdentifierCache {
366
376
  identifier = /*#__NOINLINE__*/makeStableRecordIdentifier(keyInfo, 'record', false);
367
377
  }
368
378
  addResourceToCache(this._cache, identifier);
369
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
370
- // eslint-disable-next-line no-console
371
- console.groupEnd();
379
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
380
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
381
+ // eslint-disable-next-line no-console
382
+ console.groupEnd();
383
+ }
372
384
  }
373
385
  return identifier;
374
386
  }
@@ -462,9 +474,11 @@ class IdentifierCache {
462
474
 
463
475
  /*#__NOINLINE__*/
464
476
  addResourceToCache(this._cache, identifier);
465
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
466
- // eslint-disable-next-line no-console
467
- console.log(`Identifiers: created identifier ${String(identifier)} for newly generated resource`, data);
477
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
478
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
479
+ // eslint-disable-next-line no-console
480
+ console.log(`Identifiers: created identifier ${String(identifier)} for newly generated resource`, data);
481
+ }
468
482
  }
469
483
  return identifier;
470
484
  }
@@ -511,9 +525,11 @@ class IdentifierCache {
511
525
  if (hadLid) {
512
526
  data.lid = identifier.lid;
513
527
  }
514
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
515
- // eslint-disable-next-line no-console
516
- console.log(`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`, data);
528
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
529
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
530
+ // eslint-disable-next-line no-console
531
+ console.log(`Identifiers: merged identifiers ${generatedIdentifier.lid} and ${existingIdentifier.lid} for resource into ${identifier.lid}`, data);
532
+ }
517
533
  }
518
534
  }
519
535
  const id = identifier.id;
@@ -523,9 +539,11 @@ class IdentifierCache {
523
539
 
524
540
  // add to our own secondary lookup table
525
541
  if (id !== newId && newId !== null) {
526
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
527
- // eslint-disable-next-line no-console
528
- console.log(`Identifiers: updated id for identifier ${identifier.lid} from '${String(id)}' to '${String(newId)}' for resource`, data);
542
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
543
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
544
+ // eslint-disable-next-line no-console
545
+ console.log(`Identifiers: updated id for identifier ${identifier.lid} from '${String(id)}' to '${String(newId)}' for resource`, data);
546
+ }
529
547
  }
530
548
  const typeSet = this._cache.resourcesByType[identifier.type];
531
549
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
@@ -537,9 +555,11 @@ class IdentifierCache {
537
555
  if (id !== null) {
538
556
  typeSet.id.delete(id);
539
557
  }
540
- } else if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
541
- // eslint-disable-next-line no-console
542
- console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data);
558
+ } else if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
559
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
560
+ // eslint-disable-next-line no-console
561
+ console.log(`Identifiers: updated identifier ${identifier.lid} resource`, data);
562
+ }
543
563
  }
544
564
  return identifier;
545
565
  }
@@ -621,11 +641,12 @@ class IdentifierCache {
621
641
  identifier[DEBUG_STALE_CACHE_OWNER] = identifier[CACHE_OWNER];
622
642
  }
623
643
  identifier[CACHE_OWNER] = undefined;
624
- IDENTIFIERS.delete(identifier);
625
644
  this._forget(identifier, 'record');
626
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
627
- // eslint-disable-next-line no-console
628
- console.log(`Identifiers: released identifier ${identifierObject.lid}`);
645
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
646
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
647
+ // eslint-disable-next-line no-console
648
+ console.log(`Identifiers: released identifier ${identifierObject.lid}`);
649
+ }
629
650
  }
630
651
  }
631
652
  destroy() {
@@ -637,20 +658,17 @@ class IdentifierCache {
637
658
  }
638
659
  }
639
660
  function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated) {
640
- IDENTIFIERS.add(recordIdentifier);
641
661
  if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
642
662
  // we enforce immutability in dev
643
663
  // but preserve our ability to do controlled updates to the reference
644
664
  let wrapper = {
645
- get lid() {
646
- return recordIdentifier.lid;
647
- },
665
+ type: recordIdentifier.type,
666
+ lid: recordIdentifier.lid,
648
667
  get id() {
649
668
  return recordIdentifier.id;
650
- },
651
- get type() {
652
- return recordIdentifier.type;
653
- },
669
+ }
670
+ };
671
+ const proto = {
654
672
  get [CACHE_OWNER]() {
655
673
  return recordIdentifier[CACHE_OWNER];
656
674
  },
@@ -662,9 +680,15 @@ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated)
662
680
  },
663
681
  set [DEBUG_STALE_CACHE_OWNER](value) {
664
682
  recordIdentifier[DEBUG_STALE_CACHE_OWNER] = value;
683
+ },
684
+ get [DEBUG_CLIENT_ORIGINATED]() {
685
+ return clientOriginated;
686
+ },
687
+ get [DEBUG_IDENTIFIER_BUCKET]() {
688
+ return bucket;
665
689
  }
666
690
  };
667
- Object.defineProperty(wrapper, 'toString', {
691
+ Object.defineProperty(proto, 'toString', {
668
692
  enumerable: false,
669
693
  value: () => {
670
694
  const {
@@ -675,7 +699,7 @@ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated)
675
699
  return `${clientOriginated ? '[CLIENT_ORIGINATED] ' : ''}${String(type)}:${String(id)} (${lid})`;
676
700
  }
677
701
  });
678
- Object.defineProperty(wrapper, 'toJSON', {
702
+ Object.defineProperty(proto, 'toJSON', {
679
703
  enumerable: false,
680
704
  value: () => {
681
705
  const {
@@ -690,9 +714,7 @@ function makeStableRecordIdentifier(recordIdentifier, bucket, clientOriginated)
690
714
  };
691
715
  }
692
716
  });
693
- wrapper[DEBUG_CLIENT_ORIGINATED] = clientOriginated;
694
- wrapper[DEBUG_IDENTIFIER_BUCKET] = bucket;
695
- IDENTIFIERS.add(wrapper);
717
+ Object.setPrototypeOf(wrapper, proto);
696
718
  DEBUG_MAP.set(wrapper, recordIdentifier);
697
719
  wrapper = freeze(wrapper);
698
720
  return wrapper;
@@ -775,9 +797,11 @@ function detectMerge(cache, keyInfo, identifier, data) {
775
797
  }
776
798
  function getIdentifierFromLid(cache, lid, resource) {
777
799
  const identifier = cache.resources.get(lid);
778
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS)) {
779
- // eslint-disable-next-line no-console
780
- console.log(`Identifiers: cache ${identifier ? 'HIT' : 'MISS'} - Non-Stable ${lid}`, resource);
800
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_IDENTIFIERS)) {
801
+ if (getGlobalConfig().WarpDrive.debug.LOG_IDENTIFIERS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_IDENTIFIERS) {
802
+ // eslint-disable-next-line no-console
803
+ console.log(`Identifiers: cache ${identifier ? 'HIT' : 'MISS'} - Non-Stable ${lid}`, resource);
804
+ }
781
805
  }
782
806
  return identifier || null;
783
807
  }
@@ -796,88 +820,473 @@ function addResourceToCache(cache, identifier) {
796
820
  typeSet.id.set(identifier.id, identifier);
797
821
  }
798
822
  }
823
+ const TEXT_COLORS = {
824
+ TEXT: 'inherit',
825
+ notify: ['white', 'white', 'inherit', 'magenta', 'inherit'],
826
+ 'reactive-ui': ['white', 'white', 'inherit', 'magenta', 'inherit'],
827
+ graph: ['white', 'white', 'inherit', 'magenta', 'inherit'],
828
+ request: ['white', 'white', 'inherit', 'magenta', 'inherit'],
829
+ cache: ['white', 'white', 'inherit', 'magenta', 'inherit']
830
+ };
831
+ const BG_COLORS = {
832
+ TEXT: 'transparent',
833
+ notify: ['dimgray', 'cadetblue', 'transparent', 'transparent', 'transparent'],
834
+ 'reactive-ui': ['dimgray', 'cadetblue', 'transparent', 'transparent', 'transparent'],
835
+ graph: ['dimgray', 'cadetblue', 'transparent', 'transparent', 'transparent'],
836
+ request: ['dimgray', 'cadetblue', 'transparent', 'transparent', 'transparent'],
837
+ cache: ['dimgray', 'cadetblue', 'transparent', 'transparent', 'transparent']
838
+ };
839
+ const NOTIFY_BORDER = {
840
+ TEXT: 0,
841
+ notify: [3, 2, 0, 0, 0],
842
+ 'reactive-ui': [3, 2, 0, 0, 0],
843
+ graph: [3, 2, 0, 0, 0],
844
+ request: [3, 2, 0, 0, 0],
845
+ cache: [3, 2, 0, 0, 0]
846
+ };
847
+ const LIGHT_DARK_ALT = {
848
+ lightgreen: 'green',
849
+ green: 'lightgreen'
850
+ };
851
+ function badge(isLight, color, bgColor, border) {
852
+ return [`color: ${correctColor(isLight, color)}; background-color: ${correctColor(isLight, bgColor)}; padding: ${border}px ${2 * border}px; border-radius: ${border}px;`, `color: ${TEXT_COLORS.TEXT}; background-color: ${BG_COLORS.TEXT};`];
853
+ }
854
+ function colorForBucket(isLight, scope, bucket) {
855
+ if (scope === 'notify') {
856
+ return bucket === 'added' ? badge(isLight, 'lightgreen', 'transparent', 0) : bucket === 'removed' ? badge(isLight, 'red', 'transparent', 0) : badge(isLight, TEXT_COLORS[scope][2], BG_COLORS[scope][2], NOTIFY_BORDER[scope][2]);
857
+ }
858
+ if (scope === 'reactive-ui') {
859
+ return bucket === 'created' ? badge(isLight, 'lightgreen', 'transparent', 0) : bucket === 'disconnected' ? badge(isLight, 'red', 'transparent', 0) : badge(isLight, TEXT_COLORS[scope][2], BG_COLORS[scope][2], NOTIFY_BORDER[scope][2]);
860
+ }
861
+ if (scope === 'cache') {
862
+ return bucket === 'inserted' ? badge(isLight, 'lightgreen', 'transparent', 0) : bucket === 'removed' ? badge(isLight, 'red', 'transparent', 0) : badge(isLight, TEXT_COLORS[scope][2], BG_COLORS[scope][2], NOTIFY_BORDER[scope][2]);
863
+ }
864
+ return badge(isLight, TEXT_COLORS[scope][3], BG_COLORS[scope][3], NOTIFY_BORDER[scope][3]);
865
+ }
866
+ function logGroup(scope, prefix, subScop1, subScop2, subScop3, subScop4) {
867
+ // eslint-disable-next-line no-console
868
+ console.groupCollapsed(..._log(scope, prefix, subScop1, subScop2, subScop3, subScop4));
869
+ }
870
+ function log(scope, prefix, subScop1, subScop2, subScop3, subScop4) {
871
+ // eslint-disable-next-line no-console
872
+ console.log(..._log(scope, prefix, subScop1, subScop2, subScop3, subScop4));
873
+ }
874
+ function correctColor(isLight, color) {
875
+ if (!isLight) {
876
+ return color;
877
+ }
878
+ return color in LIGHT_DARK_ALT ? LIGHT_DARK_ALT[color] : color;
879
+ }
880
+ function isLightMode() {
881
+ if (window?.matchMedia?.('(prefers-color-scheme: light)').matches) {
882
+ return true;
883
+ }
884
+ return false;
885
+ }
886
+ function _log(scope, prefix, subScop1, subScop2, subScop3, subScop4) {
887
+ const isLight = isLightMode();
888
+ switch (scope) {
889
+ case 'reactive-ui':
890
+ case 'notify':
891
+ {
892
+ const scopePath = prefix ? `[${prefix}] ${scope}` : scope;
893
+ const path = subScop4 ? `${subScop3}.${subScop4}` : subScop3;
894
+ return [`%c@warp%c-%cdrive%c %c${scopePath}%c %c${subScop1}%c %c${subScop2}%c %c${path}%c`, ...badge(isLight, 'lightgreen', 'transparent', 0), ...badge(isLight, 'magenta', 'transparent', 0), ...badge(isLight, TEXT_COLORS[scope][0], BG_COLORS[scope][0], NOTIFY_BORDER[scope][0]), ...badge(isLight, TEXT_COLORS[scope][1], BG_COLORS[scope][1], NOTIFY_BORDER[scope][1]), ...badge(isLight, TEXT_COLORS[scope][2], BG_COLORS[scope][2], NOTIFY_BORDER[scope][2]), ...colorForBucket(isLight, scope, path)];
895
+ }
896
+ case 'cache':
897
+ {
898
+ const scopePath = prefix ? `${scope} (${prefix})` : scope;
899
+ return [`%c@warp%c-%cdrive%c %c${scopePath}%c %c${subScop1}%c %c${subScop2}%c %c${subScop3}%c %c${subScop4}%c`, ...badge(isLight, 'lightgreen', 'transparent', 0), ...badge(isLight, 'magenta', 'transparent', 0), ...badge(isLight, TEXT_COLORS[scope][0], BG_COLORS[scope][0], NOTIFY_BORDER[scope][0]), ...badge(isLight, TEXT_COLORS[scope][1], BG_COLORS[scope][1], NOTIFY_BORDER[scope][1]), ...badge(isLight, TEXT_COLORS[scope][2], BG_COLORS[scope][2], NOTIFY_BORDER[scope][2]), ...colorForBucket(isLight, scope, subScop3), ...badge(isLight, TEXT_COLORS[scope][4], BG_COLORS[scope][4], NOTIFY_BORDER[scope][4])];
900
+ }
901
+ }
902
+ return [];
903
+ }
799
904
 
800
905
  /**
801
- @module @ember-data/store
802
- */
906
+ * @module @ember-data/store
907
+ */
908
+ function urlFromLink(link) {
909
+ if (typeof link === 'string') return link;
910
+ return link.href;
911
+ }
803
912
 
804
913
  /**
805
- A `RecordReference` is a low-level API that allows users and
806
- addon authors to perform meta-operations on a record.
914
+ * A Document is a class that wraps the response content from a request to the API
915
+ * returned by `Cache.put` or `Cache.peek`, converting resource-identifiers into
916
+ * record instances.
917
+ *
918
+ * It is not directly instantiated by the user, and its properties should not
919
+ * be directly modified. Whether individual properties are mutable or not is
920
+ * determined by the record instance itself.
921
+ *
922
+ * @public
923
+ * @class ReactiveDocument
924
+ */
925
+ class ReactiveDocument {
926
+ /**
927
+ * The links object for this document, if any
928
+ *
929
+ * e.g.
930
+ *
931
+ * ```
932
+ * {
933
+ * self: '/articles?page[number]=3',
934
+ * }
935
+ * ```
936
+ *
937
+ * @property links
938
+ * @type {object|undefined} - a links object
939
+ * @public
940
+ */
807
941
 
808
- @class RecordReference
809
- @public
810
- */
811
- class RecordReference {
812
- // unsubscribe token given to us by the notification manager
813
- ___token;
814
- ___identifier;
815
- constructor(store, identifier) {
816
- this.store = store;
817
- this.___identifier = identifier;
818
- this.___token = store.notifications.subscribe(identifier, (_, bucket, notifiedKey) => {
819
- if (bucket === 'identity' || bucket === 'attributes' && notifiedKey === 'id') {
820
- this._ref++;
821
- }
822
- });
823
- }
824
- destroy() {
825
- this.store.notifications.unsubscribe(this.___token);
826
- }
827
- get type() {
828
- return this.identifier().type;
829
- }
942
+ /**
943
+ * The primary data for this document, if any.
944
+ *
945
+ * If this document has no primary data (e.g. because it is an error document)
946
+ * this property will be `undefined`.
947
+ *
948
+ * For collections this will be an array of record instances,
949
+ * for single resource requests it will be a single record instance or null.
950
+ *
951
+ * @property data
952
+ * @public
953
+ * @type {object|Array<object>|null|undefined} - a data object
954
+ */
830
955
 
831
956
  /**
832
- The `id` of the record that this reference refers to.
833
- Together, the `type` and `id` properties form a composite key for
834
- the identity map.
835
- Example
836
- ```javascript
837
- let userRef = store.getReference('user', 1);
838
- userRef.id(); // '1'
839
- ```
840
- @method id
841
- @public
842
- @return {String} The id of the record.
843
- */
844
- id() {
845
- this._ref; // consume the tracked prop
846
- return this.___identifier.id;
957
+ * The errors returned by the API for this request, if any
958
+ *
959
+ * @property errors
960
+ * @public
961
+ * @type {object|undefined} - an errors object
962
+ */
963
+
964
+ /**
965
+ * The meta object for this document, if any
966
+ *
967
+ * @property meta
968
+ * @public
969
+ * @type {object|undefined} - a meta object
970
+ */
971
+
972
+ /**
973
+ * The identifier associated with this document, if any
974
+ *
975
+ * @property identifier
976
+ * @public
977
+ * @type {StableDocumentIdentifier|null}
978
+ */
979
+
980
+ constructor(store, identifier, localCache) {
981
+ this._store = store;
982
+ this._localCache = localCache;
983
+ this.identifier = identifier;
984
+
985
+ // TODO if we ever enable auto-cleanup of the cache, we will need to tear this down
986
+ // in a destroy method
987
+ if (identifier) {
988
+ store.notifications.subscribe(identifier, (_identifier, type) => {
989
+ switch (type) {
990
+ case 'updated':
991
+ // FIXME in the case of a collection we need to notify it's length
992
+ // and have it recalc
993
+ notifySignal(this, 'data');
994
+ notifySignal(this, 'links');
995
+ notifySignal(this, 'meta');
996
+ notifySignal(this, 'errors');
997
+ break;
998
+ }
999
+ });
1000
+ }
1001
+ }
1002
+ async #request(link, options) {
1003
+ const href = this.links?.[link];
1004
+ if (!href) {
1005
+ return null;
1006
+ }
1007
+ options.method = options.method || 'GET';
1008
+ Object.assign(options, {
1009
+ url: urlFromLink(href)
1010
+ });
1011
+ const response = await this._store.request(options);
1012
+ return response.content;
847
1013
  }
848
1014
 
849
1015
  /**
850
- The `identifier` of the record that this reference refers to.
851
- Together, the `type` and `id` properties form a composite key for
852
- the identity map.
853
- Example
854
- ```javascript
855
- let userRef = store.getReference('user', 1);
856
- userRef.identifier(); // '1'
857
- ```
858
- @method identifier
859
- @public
860
- @return {String} The identifier of the record.
861
- */
862
- identifier() {
863
- return this.___identifier;
1016
+ * Fetches the related link for this document, returning a promise that resolves
1017
+ * with the document when the request completes. If no related link is present,
1018
+ * will fallback to the self link if present
1019
+ *
1020
+ * @method fetch
1021
+ * @public
1022
+ * @param {object} options
1023
+ * @return Promise<Document>
1024
+ */
1025
+ fetch(options = {}) {
1026
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1027
+ if (!test) {
1028
+ throw new Error(`No self or related link`);
1029
+ }
1030
+ })(this.links?.related || this.links?.self) : {};
1031
+ options.cacheOptions = options.cacheOptions || {};
1032
+ options.cacheOptions.key = this.identifier?.lid;
1033
+ return this.#request(this.links.related ? 'related' : 'self', options);
864
1034
  }
865
1035
 
866
1036
  /**
867
- How the reference will be looked up when it is loaded. Currently
868
- this always returns `identity` to signify that a record will be
869
- loaded by its `type` and `id`.
870
- Example
871
- ```javascript
872
- const userRef = store.getReference('user', 1);
873
- userRef.remoteType(); // 'identity'
874
- ```
875
- @method remoteType
876
- @public
877
- @return {String} 'identity'
878
- */
879
- remoteType() {
880
- return 'identity';
1037
+ * Fetches the next link for this document, returning a promise that resolves
1038
+ * with the new document when the request completes, or null if there is no
1039
+ * next link.
1040
+ *
1041
+ * @method next
1042
+ * @public
1043
+ * @param {object} options
1044
+ * @return Promise<Document | null>
1045
+ */
1046
+ next(options = {}) {
1047
+ return this.#request('next', options);
1048
+ }
1049
+
1050
+ /**
1051
+ * Fetches the prev link for this document, returning a promise that resolves
1052
+ * with the new document when the request completes, or null if there is no
1053
+ * prev link.
1054
+ *
1055
+ * @method prev
1056
+ * @public
1057
+ * @param {object} options
1058
+ * @return Promise<Document | null>
1059
+ */
1060
+ prev(options = {}) {
1061
+ return this.#request('prev', options);
1062
+ }
1063
+
1064
+ /**
1065
+ * Fetches the first link for this document, returning a promise that resolves
1066
+ * with the new document when the request completes, or null if there is no
1067
+ * first link.
1068
+ *
1069
+ * @method first
1070
+ * @public
1071
+ * @param {object} options
1072
+ * @return Promise<Document | null>
1073
+ */
1074
+ first(options = {}) {
1075
+ return this.#request('first', options);
1076
+ }
1077
+
1078
+ /**
1079
+ * Fetches the last link for this document, returning a promise that resolves
1080
+ * with the new document when the request completes, or null if there is no
1081
+ * last link.
1082
+ *
1083
+ * @method last
1084
+ * @public
1085
+ * @param {object} options
1086
+ * @return Promise<Document | null>
1087
+ */
1088
+ last(options = {}) {
1089
+ return this.#request('last', options);
1090
+ }
1091
+
1092
+ /**
1093
+ * Implemented for `JSON.stringify` support.
1094
+ *
1095
+ * Returns the JSON representation of the document wrapper.
1096
+ *
1097
+ * This is a shallow serialization, it does not deeply serialize
1098
+ * the document's contents, leaving that to the individual record
1099
+ * instances to determine how to do, if at all.
1100
+ *
1101
+ * @method toJSON
1102
+ * @public
1103
+ * @return
1104
+ */
1105
+ toJSON() {
1106
+ const data = {};
1107
+ data.identifier = this.identifier;
1108
+ if (this.data !== undefined) {
1109
+ data.data = this.data;
1110
+ }
1111
+ if (this.links !== undefined) {
1112
+ data.links = this.links;
1113
+ }
1114
+ if (this.errors !== undefined) {
1115
+ data.errors = this.errors;
1116
+ }
1117
+ if (this.meta !== undefined) {
1118
+ data.meta = this.meta;
1119
+ }
1120
+ return data;
1121
+ }
1122
+ }
1123
+ defineSubscription(ReactiveDocument.prototype, 'errors', {
1124
+ get() {
1125
+ const {
1126
+ identifier
1127
+ } = this;
1128
+ if (!identifier) {
1129
+ const {
1130
+ document
1131
+ } = this._localCache;
1132
+ if ('errors' in document) {
1133
+ return document.errors;
1134
+ }
1135
+ return;
1136
+ }
1137
+ const doc = this._store.cache.peek(identifier);
1138
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1139
+ if (!test) {
1140
+ throw new Error(`No cache data was found for the document '${identifier.lid}'`);
1141
+ }
1142
+ })(doc) : {};
1143
+ return 'errors' in doc ? doc.errors : undefined;
1144
+ }
1145
+ });
1146
+ defineSubscription(ReactiveDocument.prototype, 'data', {
1147
+ get() {
1148
+ const {
1149
+ identifier,
1150
+ _localCache
1151
+ } = this;
1152
+ const doc = identifier ? this._store.cache.peek(identifier) : _localCache.document;
1153
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1154
+ if (!test) {
1155
+ throw new Error(`No cache data was found for the document '${identifier?.lid ?? '<uncached document>'}'`);
1156
+ }
1157
+ })(doc) : {};
1158
+ const data = 'data' in doc ? doc.data : undefined;
1159
+ if (Array.isArray(data)) {
1160
+ return this._store.recordArrayManager.getCollection({
1161
+ type: identifier ? identifier.lid : _localCache.request.url,
1162
+ identifiers: data.slice(),
1163
+ doc: identifier ? undefined : doc,
1164
+ identifier: identifier ?? null
1165
+ });
1166
+ } else if (data) {
1167
+ return this._store.peekRecord(data);
1168
+ } else {
1169
+ return data;
1170
+ }
1171
+ }
1172
+ });
1173
+ defineSubscription(ReactiveDocument.prototype, 'links', {
1174
+ get() {
1175
+ const {
1176
+ identifier
1177
+ } = this;
1178
+ if (!identifier) {
1179
+ return this._localCache.document.links;
1180
+ }
1181
+ const data = this._store.cache.peek(identifier);
1182
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1183
+ if (!test) {
1184
+ throw new Error(`No cache data was found for the document '${identifier.lid}'`);
1185
+ }
1186
+ })(data) : {};
1187
+ return data.links;
1188
+ }
1189
+ });
1190
+ defineSubscription(ReactiveDocument.prototype, 'meta', {
1191
+ get() {
1192
+ const {
1193
+ identifier
1194
+ } = this;
1195
+ if (!identifier) {
1196
+ return this._localCache.document.meta;
1197
+ }
1198
+ const data = this._store.cache.peek(identifier);
1199
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
1200
+ if (!test) {
1201
+ throw new Error(`No cache data was found for the document '${identifier.lid}'`);
1202
+ }
1203
+ })(data) : {};
1204
+ return data.meta;
1205
+ }
1206
+ });
1207
+
1208
+ /**
1209
+ @module @ember-data/store
1210
+ */
1211
+
1212
+ /**
1213
+ A `RecordReference` is a low-level API that allows users and
1214
+ addon authors to perform meta-operations on a record.
1215
+
1216
+ @class RecordReference
1217
+ @public
1218
+ */
1219
+ class RecordReference {
1220
+ // unsubscribe token given to us by the notification manager
1221
+ ___token;
1222
+ ___identifier;
1223
+ constructor(store, identifier) {
1224
+ this.store = store;
1225
+ this.___identifier = identifier;
1226
+ this.___token = store.notifications.subscribe(identifier, (_, bucket, notifiedKey) => {
1227
+ if (bucket === 'identity' || bucket === 'attributes' && notifiedKey === 'id') {
1228
+ this._ref++;
1229
+ }
1230
+ });
1231
+ }
1232
+ destroy() {
1233
+ this.store.notifications.unsubscribe(this.___token);
1234
+ }
1235
+ get type() {
1236
+ return this.identifier().type;
1237
+ }
1238
+
1239
+ /**
1240
+ The `id` of the record that this reference refers to.
1241
+ Together, the `type` and `id` properties form a composite key for
1242
+ the identity map.
1243
+ Example
1244
+ ```javascript
1245
+ let userRef = store.getReference('user', 1);
1246
+ userRef.id(); // '1'
1247
+ ```
1248
+ @method id
1249
+ @public
1250
+ @return {String} The id of the record.
1251
+ */
1252
+ id() {
1253
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
1254
+ this._ref; // consume the tracked prop
1255
+ return this.___identifier.id;
1256
+ }
1257
+
1258
+ /**
1259
+ The `identifier` of the record that this reference refers to.
1260
+ Together, the `type` and `id` properties form a composite key for
1261
+ the identity map.
1262
+ Example
1263
+ ```javascript
1264
+ let userRef = store.getReference('user', 1);
1265
+ userRef.identifier(); // '1'
1266
+ ```
1267
+ @method identifier
1268
+ @public
1269
+ @return {String} The identifier of the record.
1270
+ */
1271
+ identifier() {
1272
+ return this.___identifier;
1273
+ }
1274
+
1275
+ /**
1276
+ How the reference will be looked up when it is loaded. Currently
1277
+ this always returns `identity` to signify that a record will be
1278
+ loaded by its `type` and `id`.
1279
+ Example
1280
+ ```javascript
1281
+ const userRef = store.getReference('user', 1);
1282
+ userRef.remoteType(); // 'identity'
1283
+ ```
1284
+ @method remoteType
1285
+ @public
1286
+ @return {String} 'identity'
1287
+ */
1288
+ remoteType() {
1289
+ return 'identity';
881
1290
  }
882
1291
 
883
1292
  /**
@@ -1176,12 +1585,13 @@ function storeFor(record) {
1176
1585
  return store;
1177
1586
  }
1178
1587
  class InstanceCache {
1179
- __instances = {
1180
- record: new Map(),
1181
- reference: new WeakMap()
1182
- };
1183
1588
  constructor(store) {
1184
1589
  this.store = store;
1590
+ this.__instances = {
1591
+ record: new Map(),
1592
+ reference: new WeakMap(),
1593
+ document: new Map()
1594
+ };
1185
1595
  this._storeWrapper = new CacheCapabilitiesManager(this.store);
1186
1596
  store.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => {
1187
1597
  let keptIdentifier = identifier;
@@ -1236,6 +1646,14 @@ class InstanceCache {
1236
1646
  peek(identifier) {
1237
1647
  return this.__instances.record.get(identifier);
1238
1648
  }
1649
+ getDocument(identifier) {
1650
+ let doc = this.__instances.document.get(identifier);
1651
+ if (!doc) {
1652
+ doc = new ReactiveDocument(this.store, identifier, null);
1653
+ this.__instances.document.set(identifier, doc);
1654
+ }
1655
+ return doc;
1656
+ }
1239
1657
  getRecord(identifier, properties) {
1240
1658
  let record = this.__instances.record.get(identifier);
1241
1659
  if (!record) {
@@ -1251,9 +1669,16 @@ class InstanceCache {
1251
1669
  setCacheFor(record, cache);
1252
1670
  StoreMap.set(record, this.store);
1253
1671
  this.__instances.record.set(identifier, record);
1254
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1255
- // eslint-disable-next-line no-console
1256
- console.log(`InstanceCache: created Record for ${String(identifier)}`, properties);
1672
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1673
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1674
+ logGroup('reactive-ui', '', identifier.type, identifier.lid, 'created', '');
1675
+ // eslint-disable-next-line no-console
1676
+ console.log({
1677
+ properties
1678
+ });
1679
+ // eslint-disable-next-line no-console
1680
+ console.groupEnd();
1681
+ }
1257
1682
  }
1258
1683
  }
1259
1684
  return record;
@@ -1299,9 +1724,10 @@ class InstanceCache {
1299
1724
  this.store.identifierCache.forgetRecordIdentifier(identifier);
1300
1725
  removeRecordDataFor(identifier);
1301
1726
  this.store._requestCache._clearEntries(identifier);
1302
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1303
- // eslint-disable-next-line no-console
1304
- console.log(`InstanceCache: disconnected ${String(identifier)}`);
1727
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1728
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1729
+ log('reactive-ui', '', identifier.type, identifier.lid, 'disconnected', '');
1730
+ }
1305
1731
  }
1306
1732
  }
1307
1733
  unloadRecord(identifier) {
@@ -1317,9 +1743,11 @@ class InstanceCache {
1317
1743
  })() : {};
1318
1744
  }
1319
1745
  }
1320
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1321
- // eslint-disable-next-line no-console
1322
- console.groupCollapsed(`InstanceCache: unloading record for ${String(identifier)}`);
1746
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1747
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1748
+ // eslint-disable-next-line no-console
1749
+ console.groupCollapsed(`InstanceCache: unloading record for ${String(identifier)}`);
1750
+ }
1323
1751
  }
1324
1752
 
1325
1753
  // TODO is this join still necessary?
@@ -1332,27 +1760,33 @@ class InstanceCache {
1332
1760
  StoreMap.delete(record);
1333
1761
  RecordCache.delete(record);
1334
1762
  removeRecordDataFor(record);
1335
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1336
- // eslint-disable-next-line no-console
1337
- console.log(`InstanceCache: destroyed record for ${String(identifier)}`);
1763
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1764
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1765
+ // eslint-disable-next-line no-console
1766
+ console.log(`InstanceCache: destroyed record for ${String(identifier)}`);
1767
+ }
1338
1768
  }
1339
1769
  }
1340
1770
  if (cache) {
1341
1771
  cache.unloadRecord(identifier);
1342
1772
  removeRecordDataFor(identifier);
1343
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1344
- // eslint-disable-next-line no-console
1345
- console.log(`InstanceCache: destroyed cache for ${String(identifier)}`);
1773
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1774
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1775
+ // eslint-disable-next-line no-console
1776
+ console.log(`InstanceCache: destroyed cache for ${String(identifier)}`);
1777
+ }
1346
1778
  }
1347
1779
  } else {
1348
1780
  this.disconnect(identifier);
1349
1781
  }
1350
1782
  this.store._requestCache._clearEntries(identifier);
1351
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1352
- // eslint-disable-next-line no-console
1353
- console.log(`InstanceCache: unloaded RecordData for ${String(identifier)}`);
1354
- // eslint-disable-next-line no-console
1355
- console.groupEnd();
1783
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1784
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1785
+ // eslint-disable-next-line no-console
1786
+ console.log(`InstanceCache: unloaded RecordData for ${String(identifier)}`);
1787
+ // eslint-disable-next-line no-console
1788
+ console.groupEnd();
1789
+ }
1356
1790
  }
1357
1791
  });
1358
1792
  }
@@ -1407,9 +1841,11 @@ class InstanceCache {
1407
1841
  warn(`Your ${type} record was saved to the server, but the response does not have an id.`, !(oldId !== null && id === null));
1408
1842
  return;
1409
1843
  }
1410
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE)) {
1411
- // eslint-disable-next-line no-console
1412
- console.log(`InstanceCache: updating id to '${id}' for record ${String(identifier)}`);
1844
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_INSTANCE_CACHE)) {
1845
+ if (getGlobalConfig().WarpDrive.debug.LOG_INSTANCE_CACHE || globalThis.getWarpDriveRuntimeConfig().debug.LOG_INSTANCE_CACHE) {
1846
+ // eslint-disable-next-line no-console
1847
+ console.log(`InstanceCache: updating id to '${id}' for record ${String(identifier)}`);
1848
+ }
1413
1849
  }
1414
1850
  const existingIdentifier = this.store.identifierCache.peekRecordIdentifier({
1415
1851
  type,
@@ -1726,7 +2162,9 @@ class CacheManager {
1726
2162
  peek(identifier) {
1727
2163
  return this.#cache.peek(identifier);
1728
2164
  }
1729
-
2165
+ peekRemoteState(identifier) {
2166
+ return this.#cache.peekRemoteState(identifier);
2167
+ }
1730
2168
  /**
1731
2169
  * Peek the Cache for the existing request data associated with
1732
2170
  * a cacheable request
@@ -1949,6 +2387,19 @@ class CacheManager {
1949
2387
  return this.#cache.getAttr(identifier, propertyName);
1950
2388
  }
1951
2389
 
2390
+ /**
2391
+ * Retrieve the remote state for an attribute from the cache
2392
+ *
2393
+ * @method getRemoteAttr
2394
+ * @public
2395
+ * @param identifier
2396
+ * @param propertyName
2397
+ * @return {unknown}
2398
+ */
2399
+ getRemoteAttr(identifier, propertyName) {
2400
+ return this.#cache.getRemoteAttr(identifier, propertyName);
2401
+ }
2402
+
1952
2403
  /**
1953
2404
  * Mutate the data for an attribute in the cache
1954
2405
  *
@@ -2073,6 +2524,19 @@ class CacheManager {
2073
2524
  return this.#cache.getRelationship(identifier, propertyName);
2074
2525
  }
2075
2526
 
2527
+ /**
2528
+ * Query the cache for the remote state of a relationship property
2529
+ *
2530
+ * @method getRelationship
2531
+ * @public
2532
+ * @param identifier
2533
+ * @param propertyName
2534
+ * @return resource relationship object
2535
+ */
2536
+ getRemoteRelationship(identifier, propertyName) {
2537
+ return this.#cache.getRemoteRelationship(identifier, propertyName);
2538
+ }
2539
+
2076
2540
  // Resource State
2077
2541
  // ===============
2078
2542
 
@@ -2156,28 +2620,52 @@ class CacheManager {
2156
2620
  /**
2157
2621
  * @module @ember-data/store
2158
2622
  */
2159
- // eslint-disable-next-line no-restricted-imports
2160
- let tokenId = 0;
2161
- const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
2623
+
2162
2624
  function isCacheOperationValue(value) {
2163
- return CacheOperations.has(value);
2625
+ return value === 'added' || value === 'state' || value === 'updated' || value === 'removed' || value === 'invalidated';
2164
2626
  }
2165
2627
  function runLoopIsFlushing() {
2166
2628
  //@ts-expect-error
2167
2629
  return !!_backburner.currentInstance && _backburner._autorun !== true;
2168
2630
  }
2169
- function _unsubscribe(tokens, token, cache) {
2170
- const identifier = tokens.get(token);
2171
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS)) {
2172
- if (!identifier) {
2173
- // eslint-disable-next-line no-console
2174
- console.log('Passed unknown unsubscribe token to unsubscribe', identifier);
2631
+ function count(label) {
2632
+ // @ts-expect-error
2633
+ // eslint-disable-next-line
2634
+ globalThis.__WarpDriveMetricCountData[label] = (globalThis.__WarpDriveMetricCountData[label] || 0) + 1;
2635
+ }
2636
+ function asInternalToken(token) {
2637
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2638
+ if (!test) {
2639
+ throw new Error(`Expected a token with a 'for' property`);
2640
+ }
2641
+ })(token && typeof token === 'function' && 'for' in token) : {};
2642
+ }
2643
+ function _unsubscribe(token, cache) {
2644
+ asInternalToken(token);
2645
+ const identifier = token.for;
2646
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_NOTIFICATIONS)) {
2647
+ if (getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_NOTIFICATIONS) {
2648
+ if (!identifier) {
2649
+ // eslint-disable-next-line no-console
2650
+ console.log('Passed unknown unsubscribe token to unsubscribe', identifier);
2651
+ }
2175
2652
  }
2176
2653
  }
2177
2654
  if (identifier) {
2178
- tokens.delete(token);
2179
- const map = cache.get(identifier);
2180
- map?.delete(token);
2655
+ const callbacks = cache.get(identifier);
2656
+ if (!callbacks) {
2657
+ return;
2658
+ }
2659
+ const index = callbacks.indexOf(token);
2660
+ if (index === -1) {
2661
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2662
+ if (!test) {
2663
+ throw new Error(`Cannot unsubscribe a token that is not subscribed`);
2664
+ }
2665
+ })(index !== -1) : {};
2666
+ return;
2667
+ }
2668
+ callbacks.splice(index, 1);
2181
2669
  }
2182
2670
  }
2183
2671
 
@@ -2198,7 +2686,6 @@ class NotificationManager {
2198
2686
  this._buffered = new Map();
2199
2687
  this._hasFlush = false;
2200
2688
  this._cache = new Map();
2201
- this._tokens = new Map();
2202
2689
  }
2203
2690
 
2204
2691
  /**
@@ -2235,17 +2722,26 @@ class NotificationManager {
2235
2722
  throw new Error(`Expected to receive a stable Identifier to subscribe to`);
2236
2723
  }
2237
2724
  })(identifier === 'resource' || identifier === 'document' || isStableIdentifier(identifier) || isDocumentIdentifier(identifier)) : {};
2238
- let map = this._cache.get(identifier);
2239
- if (!map) {
2240
- map = new Map();
2241
- this._cache.set(identifier, map);
2725
+ let callbacks = this._cache.get(identifier);
2726
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2727
+ if (!test) {
2728
+ throw new Error(`expected to receive a valid callback`);
2729
+ }
2730
+ })(typeof callback === 'function') : {};
2731
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2732
+ if (!test) {
2733
+ throw new Error(`cannot subscribe with the same callback twice`);
2734
+ }
2735
+ })(!callbacks || !callbacks.includes(callback)) : {};
2736
+ // we use the callback as the cancellation token
2737
+ //@ts-expect-error
2738
+ callback.for = identifier;
2739
+ if (!callbacks) {
2740
+ callbacks = [];
2741
+ this._cache.set(identifier, callbacks);
2242
2742
  }
2243
- const unsubToken = macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? {
2244
- _tokenRef: tokenId++
2245
- } : {};
2246
- map.set(unsubToken, callback);
2247
- this._tokens.set(unsubToken, identifier);
2248
- return unsubToken;
2743
+ callbacks.push(callback);
2744
+ return callback;
2249
2745
  }
2250
2746
 
2251
2747
  /**
@@ -2257,7 +2753,7 @@ class NotificationManager {
2257
2753
  */
2258
2754
  unsubscribe(token) {
2259
2755
  if (!this.isDestroyed) {
2260
- _unsubscribe(this._tokens, token, this._cache);
2756
+ _unsubscribe(token, this._cache);
2261
2757
  }
2262
2758
  }
2263
2759
 
@@ -2279,17 +2775,15 @@ class NotificationManager {
2279
2775
  }
2280
2776
  })(!key || value === 'attributes' || value === 'relationships') : {};
2281
2777
  if (!isStableIdentifier(identifier) && !isDocumentIdentifier(identifier)) {
2282
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS)) {
2283
- // eslint-disable-next-line no-console
2284
- console.log(`Notifying: Expected to receive a stable Identifier to notify '${value}' '${key || ''}' with, but ${String(identifier)} is not in the cache`, identifier);
2778
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_NOTIFICATIONS)) {
2779
+ if (getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_NOTIFICATIONS) {
2780
+ // eslint-disable-next-line no-console
2781
+ console.log(`Notifying: Expected to receive a stable Identifier to notify '${value}' '${key || ''}' with, but ${String(identifier)} is not in the cache`, identifier);
2782
+ }
2285
2783
  }
2286
2784
  return false;
2287
2785
  }
2288
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS)) {
2289
- // eslint-disable-next-line no-console
2290
- console.log(`Buffering Notify: ${String(identifier.lid)}\t${value}\t${key || ''}`);
2291
- }
2292
- const hasSubscribers = Boolean(this._cache.get(identifier)?.size);
2786
+ const hasSubscribers = Boolean(this._cache.get(identifier)?.length);
2293
2787
  if (isCacheOperationValue(value) || hasSubscribers) {
2294
2788
  let buffer = this._buffered.get(identifier);
2295
2789
  if (!buffer) {
@@ -2297,7 +2791,29 @@ class NotificationManager {
2297
2791
  this._buffered.set(identifier, buffer);
2298
2792
  }
2299
2793
  buffer.push([value, key]);
2300
- this._scheduleNotify();
2794
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_METRIC_COUNTS)) {
2795
+ if (getGlobalConfig().WarpDrive.debug.LOG_METRIC_COUNTS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_METRIC_COUNTS) {
2796
+ count(`notify ${'type' in identifier ? identifier.type : '<document>'} ${value} ${key}`);
2797
+ }
2798
+ }
2799
+ if (!this._scheduleNotify()) {
2800
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_NOTIFICATIONS)) {
2801
+ if (getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_NOTIFICATIONS) {
2802
+ log('notify', 'buffered', `${'type' in identifier ? identifier.type : 'document'}`, identifier.lid, `${value}`, key || '');
2803
+ }
2804
+ }
2805
+ }
2806
+ } else {
2807
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_NOTIFICATIONS)) {
2808
+ if (getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_NOTIFICATIONS) {
2809
+ log('notify', 'discarded', `${'type' in identifier ? identifier.type : 'document'}`, identifier.lid, `${value}`, key || '');
2810
+ }
2811
+ }
2812
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_METRIC_COUNTS)) {
2813
+ if (getGlobalConfig().WarpDrive.debug.LOG_METRIC_COUNTS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_METRIC_COUNTS) {
2814
+ count(`DISCARDED notify ${'type' in identifier ? identifier.type : '<document>'} ${value} ${key}`);
2815
+ }
2816
+ }
2301
2817
  }
2302
2818
  return hasSubscribers;
2303
2819
  }
@@ -2308,33 +2824,36 @@ class NotificationManager {
2308
2824
  const asyncFlush = this.store._enableAsyncFlush;
2309
2825
  if (this._hasFlush) {
2310
2826
  if (asyncFlush !== false && !runLoopIsFlushing()) {
2311
- return;
2827
+ return false;
2312
2828
  }
2313
2829
  }
2314
2830
  if (asyncFlush && !runLoopIsFlushing()) {
2315
2831
  this._hasFlush = true;
2316
- return;
2832
+ return false;
2317
2833
  }
2318
2834
  this._flush();
2835
+ return true;
2319
2836
  }
2320
2837
  _flush() {
2321
- if (this._buffered.size) {
2322
- this._buffered.forEach((states, identifier) => {
2838
+ const buffered = this._buffered;
2839
+ if (buffered.size) {
2840
+ this._buffered = new Map();
2841
+ buffered.forEach((states, identifier) => {
2323
2842
  states.forEach(args => {
2324
2843
  // @ts-expect-error
2325
2844
  this._flushNotification(identifier, args[0], args[1]);
2326
2845
  });
2327
2846
  });
2328
- this._buffered = new Map();
2329
2847
  }
2330
2848
  this._hasFlush = false;
2331
2849
  this._onFlushCB?.();
2332
2850
  this._onFlushCB = undefined;
2333
2851
  }
2334
2852
  _flushNotification(identifier, value, key) {
2335
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS)) {
2336
- // eslint-disable-next-line no-console
2337
- console.log(`Notifying: ${String(identifier)}\t${value}\t${key || ''}`);
2853
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_NOTIFICATIONS)) {
2854
+ if (getGlobalConfig().WarpDrive.debug.LOG_NOTIFICATIONS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_NOTIFICATIONS) {
2855
+ log('notify', '', `${'type' in identifier ? identifier.type : 'document'}`, identifier.lid, `${value}`, key || '');
2856
+ }
2338
2857
  }
2339
2858
 
2340
2859
  // TODO for documents this will need to switch based on Identifier kind
@@ -2346,11 +2865,11 @@ class NotificationManager {
2346
2865
  });
2347
2866
  }
2348
2867
  }
2349
- const callbackMap = this._cache.get(identifier);
2350
- if (!callbackMap || !callbackMap.size) {
2868
+ const callbacks = this._cache.get(identifier);
2869
+ if (!callbacks || !callbacks.length) {
2351
2870
  return false;
2352
2871
  }
2353
- callbackMap.forEach(cb => {
2872
+ callbacks.forEach(cb => {
2354
2873
  // @ts-expect-error overload doesn't narrow within body
2355
2874
  cb(identifier, value, key);
2356
2875
  });
@@ -2358,7 +2877,6 @@ class NotificationManager {
2358
2877
  }
2359
2878
  destroy() {
2360
2879
  this.isDestroyed = true;
2361
- this._tokens.clear();
2362
2880
  this._cache.clear();
2363
2881
  }
2364
2882
  }
@@ -2372,71 +2890,6 @@ class NotificationManager {
2372
2890
  */
2373
2891
 
2374
2892
  const NativeProxy = Proxy;
2375
- var __defProp = Object.defineProperty;
2376
- var __export = (target, all) => {
2377
- for (var name in all) __defProp(target, name, {
2378
- get: all[name],
2379
- enumerable: true
2380
- });
2381
- };
2382
-
2383
- // src/runtime.ts
2384
- var runtime_exports = {};
2385
- __export(runtime_exports, {
2386
- c: () => decorateClass,
2387
- f: () => decorateFieldV1,
2388
- g: () => decorateFieldV2,
2389
- i: () => initializeDeferredDecorator,
2390
- m: () => decorateMethodV1,
2391
- n: () => decorateMethodV2,
2392
- p: () => decoratePOJO
2393
- });
2394
- var deferred = /* @__PURE__ */new WeakMap();
2395
- function deferDecorator(proto, prop, desc) {
2396
- let map = deferred.get(proto);
2397
- if (!map) {
2398
- map = /* @__PURE__ */new Map();
2399
- deferred.set(proto, map);
2400
- }
2401
- map.set(prop, desc);
2402
- }
2403
- function findDeferredDecorator(target, prop) {
2404
- let cursor = target.prototype;
2405
- while (cursor) {
2406
- let desc = deferred.get(cursor)?.get(prop);
2407
- if (desc) {
2408
- return desc;
2409
- }
2410
- cursor = cursor.prototype;
2411
- }
2412
- }
2413
- function decorateFieldV1(target, prop, decorators, initializer) {
2414
- return decorateFieldV2(target.prototype, prop, decorators, initializer);
2415
- }
2416
- function decorateFieldV2(prototype, prop, decorators, initializer) {
2417
- let desc = {
2418
- configurable: true,
2419
- enumerable: true,
2420
- writable: true,
2421
- initializer: null
2422
- };
2423
- if (initializer) {
2424
- desc.initializer = initializer;
2425
- }
2426
- for (let decorator of decorators) {
2427
- desc = decorator(prototype, prop, desc) || desc;
2428
- }
2429
- if (desc.initializer === void 0) {
2430
- Object.defineProperty(prototype, prop, desc);
2431
- } else {
2432
- deferDecorator(prototype, prop, desc);
2433
- }
2434
- }
2435
- function decorateMethodV1({
2436
- prototype
2437
- }, prop, decorators) {
2438
- return decorateMethodV2(prototype, prop, decorators);
2439
- }
2440
2893
  function decorateMethodV2(prototype, prop, decorators) {
2441
2894
  const origDesc = Object.getOwnPropertyDescriptor(prototype, prop);
2442
2895
  let desc = {
@@ -2451,46 +2904,6 @@ function decorateMethodV2(prototype, prop, decorators) {
2451
2904
  }
2452
2905
  Object.defineProperty(prototype, prop, desc);
2453
2906
  }
2454
- function initializeDeferredDecorator(target, prop) {
2455
- let desc = findDeferredDecorator(target.constructor, prop);
2456
- if (desc) {
2457
- Object.defineProperty(target, prop, {
2458
- enumerable: desc.enumerable,
2459
- configurable: desc.configurable,
2460
- writable: desc.writable,
2461
- value: desc.initializer ? desc.initializer.call(target) : void 0
2462
- });
2463
- }
2464
- }
2465
- function decorateClass(target, decorators) {
2466
- return decorators.reduce((accum, decorator) => decorator(accum) || accum, target);
2467
- }
2468
- function decoratePOJO(pojo, decorated) {
2469
- for (let [type, prop, decorators] of decorated) {
2470
- if (type === "field") {
2471
- decoratePojoField(pojo, prop, decorators);
2472
- } else {
2473
- decorateMethodV2(pojo, prop, decorators);
2474
- }
2475
- }
2476
- return pojo;
2477
- }
2478
- function decoratePojoField(pojo, prop, decorators) {
2479
- let desc = {
2480
- configurable: true,
2481
- enumerable: true,
2482
- writable: true,
2483
- initializer: () => Object.getOwnPropertyDescriptor(pojo, prop)?.value
2484
- };
2485
- for (let decorator of decorators) {
2486
- desc = decorator(pojo, prop, desc) || desc;
2487
- }
2488
- if (desc.initializer) {
2489
- desc.value = desc.initializer.call(pojo);
2490
- delete desc.initializer;
2491
- }
2492
- Object.defineProperty(pojo, prop, desc);
2493
- }
2494
2907
 
2495
2908
  /**
2496
2909
  @module @ember-data/store
@@ -2577,6 +2990,7 @@ class IdentifierArray {
2577
2990
  isDestroying = false;
2578
2991
  isDestroyed = false;
2579
2992
  _updatingPromise = null;
2993
+ identifier;
2580
2994
  [IS_COLLECTION] = true;
2581
2995
  [SOURCE];
2582
2996
  [NOTIFY]() {
@@ -2615,6 +3029,7 @@ class IdentifierArray {
2615
3029
  this.modelName = options.type;
2616
3030
  this.store = options.store;
2617
3031
  this._manager = options.manager;
3032
+ this.identifier = options.identifier || null;
2618
3033
  this[SOURCE] = options.identifiers;
2619
3034
  this[ARRAY_SIGNAL] = createSignal(this, 'length');
2620
3035
  const store = options.store;
@@ -2785,8 +3200,7 @@ class IdentifierArray {
2785
3200
  return false;
2786
3201
  }
2787
3202
  const original = target[index];
2788
- const newIdentifier = extractIdentifierFromRecord$1(value);
2789
- target[index] = newIdentifier;
3203
+ const newIdentifier = extractIdentifierFromRecord$2(value);
2790
3204
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2791
3205
  if (!test) {
2792
3206
  throw new Error(`Expected a record`);
@@ -2827,9 +3241,18 @@ class IdentifierArray {
2827
3241
  return Reflect.deleteProperty(target, prop);
2828
3242
  },
2829
3243
  getPrototypeOf() {
2830
- return IdentifierArray.prototype;
3244
+ return Array.prototype;
2831
3245
  }
2832
3246
  });
3247
+ if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
3248
+ Object.defineProperty(this, '__SHOW_ME_THE_DATA_(debug mode only)__', {
3249
+ enumerable: false,
3250
+ configurable: true,
3251
+ get() {
3252
+ return proxy.slice();
3253
+ }
3254
+ });
3255
+ }
2833
3256
  createArrayTags(proxy, _SIGNAL);
2834
3257
  this[NOTIFY] = this[NOTIFY].bind(proxy);
2835
3258
  return proxy;
@@ -2915,7 +3338,11 @@ const desc = {
2915
3338
  enumerable: true,
2916
3339
  configurable: false,
2917
3340
  get: function () {
2918
- return this;
3341
+ // here to support computed chains
3342
+ // and {{#each}}
3343
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_COMPUTED_CHAINS)) {
3344
+ return this;
3345
+ }
2919
3346
  }
2920
3347
  };
2921
3348
  compat(desc);
@@ -2966,7 +3393,7 @@ Collection.prototype.query = null;
2966
3393
  // Ensure instanceof works correctly
2967
3394
  // Object.setPrototypeOf(IdentifierArray.prototype, Array.prototype);
2968
3395
 
2969
- function assertRecordPassedToHasMany(record) {
3396
+ function assertRecordPassedToHasMany$1(record) {
2970
3397
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
2971
3398
  if (!test) {
2972
3399
  throw new Error(`All elements of a hasMany relationship must be instances of Model, you passed $${typeof record}`);
@@ -2980,11 +3407,11 @@ function assertRecordPassedToHasMany(record) {
2980
3407
  }
2981
3408
  }()) : {};
2982
3409
  }
2983
- function extractIdentifierFromRecord$1(record) {
3410
+ function extractIdentifierFromRecord$2(record) {
2984
3411
  if (!record) {
2985
3412
  return null;
2986
3413
  }
2987
- assertRecordPassedToHasMany(record);
3414
+ assertRecordPassedToHasMany$1(record);
2988
3415
  return recordIdentifierFor(record);
2989
3416
  }
2990
3417
 
@@ -3059,6 +3486,12 @@ class RecordArrayManager {
3059
3486
  this._identifiers = new Map();
3060
3487
  this._set = new Map();
3061
3488
  this._visibilitySet = new Map();
3489
+ this._subscription = this.store.notifications.subscribe('document', (identifier, type) => {
3490
+ if (type === 'updated' && this._keyedArrays.has(identifier.lid)) {
3491
+ const array = this._keyedArrays.get(identifier.lid);
3492
+ this.dirtyArray(array, 0, true);
3493
+ }
3494
+ });
3062
3495
  this._subscription = this.store.notifications.subscribe('resource', (identifier, type) => {
3063
3496
  if (type === 'added') {
3064
3497
  this._visibilitySet.set(identifier, true);
@@ -3073,11 +3506,38 @@ class RecordArrayManager {
3073
3506
  }
3074
3507
  _syncArray(array) {
3075
3508
  const pending = this._pending.get(array);
3076
- if (!pending || this.isDestroying || this.isDestroyed) {
3509
+ const isRequestArray = isCollection(array);
3510
+ if (!isRequestArray && !pending || this.isDestroying || this.isDestroyed) {
3077
3511
  return;
3078
3512
  }
3079
- sync(array, pending, this._set.get(array));
3080
- this._pending.delete(array);
3513
+
3514
+ // first flush any staged changes
3515
+ if (pending) {
3516
+ sync(array, pending, this._set.get(array));
3517
+ this._pending.delete(array);
3518
+ }
3519
+
3520
+ // then pull new state if required
3521
+ if (isRequestArray) {
3522
+ const tag = array[ARRAY_SIGNAL];
3523
+ if (tag.reason === 'cache-sync') {
3524
+ tag.reason = null;
3525
+ const doc = this.store.cache.peek(array.identifier);
3526
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
3527
+ if (!test) {
3528
+ throw new Error(`Expected to find a document for ${array.identifier.lid} but found none`);
3529
+ }
3530
+ })(doc) : {};
3531
+ const data = !('data' in doc) || !Array.isArray(doc.data) ? [] : doc.data;
3532
+ // TODO technically we should destroy here if
3533
+ // !('data' in doc) || !Array.isArray(doc.data)
3534
+ // is true.
3535
+ this.populateManagedArray(array, data, null);
3536
+ }
3537
+ }
3538
+ }
3539
+ mutate(mutation) {
3540
+ this.store.cache.mutate(mutation);
3081
3541
  }
3082
3542
 
3083
3543
  /**
@@ -3113,9 +3573,13 @@ class RecordArrayManager {
3113
3573
  }
3114
3574
  return array;
3115
3575
  }
3116
- createArray(config) {
3576
+ getCollection(config) {
3577
+ if (config.identifier && this._keyedArrays.has(config.identifier.lid)) {
3578
+ return this._keyedArrays.get(config.identifier.lid);
3579
+ }
3117
3580
  const options = {
3118
3581
  type: config.type,
3582
+ identifier: config.identifier || null,
3119
3583
  links: config.doc?.links || null,
3120
3584
  meta: config.doc?.meta || null,
3121
3585
  query: config.query || null,
@@ -3128,16 +3592,22 @@ class RecordArrayManager {
3128
3592
  const array = new Collection(options);
3129
3593
  this._managed.add(array);
3130
3594
  this._set.set(array, new Set(options.identifiers || []));
3595
+ if (config.identifier) {
3596
+ this._keyedArrays.set(config.identifier.lid, array);
3597
+ }
3131
3598
  if (config.identifiers) {
3132
3599
  associate(this._identifiers, array, config.identifiers);
3133
3600
  }
3134
3601
  return array;
3135
3602
  }
3136
- dirtyArray(array, delta) {
3603
+ dirtyArray(array, delta, shouldSyncFromCache) {
3137
3604
  if (array === FAKE_ARR) {
3138
3605
  return;
3139
3606
  }
3140
3607
  const tag = array[ARRAY_SIGNAL];
3608
+ if (shouldSyncFromCache) {
3609
+ tag.reason = 'cache-sync';
3610
+ }
3141
3611
  if (!tag.shouldReset) {
3142
3612
  tag.shouldReset = true;
3143
3613
  addTransactionCB(array[NOTIFY]);
@@ -3196,13 +3666,18 @@ class RecordArrayManager {
3196
3666
  populateManagedArray(array, identifiers, payload) {
3197
3667
  this._pending.delete(array);
3198
3668
  const source = array[SOURCE];
3669
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
3670
+ if (!test) {
3671
+ throw new Error(`The new state of the collection should not be using the same array reference as the original state.`);
3672
+ }
3673
+ })(source !== identifiers) : {};
3199
3674
  const old = source.slice();
3200
3675
  source.length = 0;
3201
3676
  fastPush(source, identifiers);
3202
3677
  this._set.set(array, new Set(identifiers));
3203
3678
  notifyArray(array);
3204
- array.meta = payload.meta || null;
3205
- array.links = payload.links || null;
3679
+ array.meta = payload?.meta || null;
3680
+ array.links = payload?.links || null;
3206
3681
  array.isLoaded = true;
3207
3682
  disassociate(this._identifiers, array, old);
3208
3683
  associate(this._identifiers, array, identifiers);
@@ -3216,7 +3691,7 @@ class RecordArrayManager {
3216
3691
  changes.delete(identifier);
3217
3692
  } else {
3218
3693
  changes.set(identifier, 'add');
3219
- this.dirtyArray(array, changes.size);
3694
+ this.dirtyArray(array, changes.size, false);
3220
3695
  }
3221
3696
  });
3222
3697
  }
@@ -3230,7 +3705,7 @@ class RecordArrayManager {
3230
3705
  changes.delete(identifier);
3231
3706
  } else {
3232
3707
  changes.set(identifier, 'del');
3233
- this.dirtyArray(array, changes.size);
3708
+ this.dirtyArray(array, changes.size, false);
3234
3709
  }
3235
3710
  });
3236
3711
  }
@@ -3335,6 +3810,9 @@ function sync(array, changes, arraySet) {
3335
3810
  */
3336
3811
  }
3337
3812
  }
3813
+ function isCollection(array) {
3814
+ return array.identifier !== null;
3815
+ }
3338
3816
 
3339
3817
  /**
3340
3818
  * @module @ember-data/store
@@ -3595,6 +4073,118 @@ function constructResource(type, id, lid) {
3595
4073
  */
3596
4074
  // this import location is deprecated but breaks in 4.8 and older
3597
4075
 
4076
+ // @ts-expect-error adding to globalThis
4077
+ globalThis.setWarpDriveLogging = setLogging;
4078
+
4079
+ // @ts-expect-error adding to globalThis
4080
+ globalThis.getWarpDriveRuntimeConfig = getRuntimeConfig;
4081
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_METRIC_COUNTS)) {
4082
+ if (getGlobalConfig().WarpDrive.debug.LOG_METRIC_COUNTS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_METRIC_COUNTS) {
4083
+ // @ts-expect-error adding to globalThis
4084
+ // eslint-disable-next-line
4085
+ globalThis.__WarpDriveMetricCountData = globalThis.__WarpDriveMetricCountData || {};
4086
+
4087
+ // @ts-expect-error adding to globalThis
4088
+ globalThis.getWarpDriveMetricCounts = () => {
4089
+ // @ts-expect-error
4090
+ // eslint-disable-next-line
4091
+ return structuredClone(globalThis.__WarpDriveMetricCountData);
4092
+ };
4093
+
4094
+ // @ts-expect-error adding to globalThis
4095
+ globalThis.resetWarpDriveMetricCounts = () => {
4096
+ // @ts-expect-error
4097
+ globalThis.__WarpDriveMetricCountData = {};
4098
+ };
4099
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.__INTERNAL_LOG_NATIVE_MAP_SET_COUNTS)) {
4100
+ if (getGlobalConfig().WarpDrive.debug.__INTERNAL_LOG_NATIVE_MAP_SET_COUNTS || globalThis.getWarpDriveRuntimeConfig().debug.__INTERNAL_LOG_NATIVE_MAP_SET_COUNTS) {
4101
+ // @ts-expect-error adding to globalThis
4102
+ globalThis.__primitiveInstanceId = 0;
4103
+ function interceptAndLog(klassName, methodName) {
4104
+ const klass = globalThis[klassName];
4105
+ if (methodName === 'constructor') {
4106
+ const instantiationLabel = `new ${klassName}()`;
4107
+ // @ts-expect-error
4108
+ globalThis[klassName] = class extends klass {
4109
+ // @ts-expect-error
4110
+ constructor(...args) {
4111
+ // eslint-disable-next-line
4112
+ super(...args);
4113
+ // @ts-expect-error
4114
+
4115
+ const instanceId = globalThis.__primitiveInstanceId++;
4116
+ // @ts-expect-error
4117
+ // eslint-disable-next-line
4118
+ globalThis.__WarpDriveMetricCountData[instantiationLabel] =
4119
+ // @ts-expect-error
4120
+ // eslint-disable-next-line
4121
+ (globalThis.__WarpDriveMetricCountData[instantiationLabel] || 0) + 1;
4122
+ // @ts-expect-error
4123
+ this.instanceName = `${klassName}:${instanceId} - ${new Error().stack?.split('\n')[2]}`;
4124
+ }
4125
+ };
4126
+ } else {
4127
+ // @ts-expect-error
4128
+ // eslint-disable-next-line
4129
+ const original = klass.prototype[methodName];
4130
+ const logName = `${klassName}.${methodName}`;
4131
+
4132
+ // @ts-expect-error
4133
+ klass.prototype[methodName] = function (...args) {
4134
+ // @ts-expect-error
4135
+ // eslint-disable-next-line
4136
+ globalThis.__WarpDriveMetricCountData[logName] = (globalThis.__WarpDriveMetricCountData[logName] || 0) + 1;
4137
+ // @ts-expect-error
4138
+ const {
4139
+ instanceName
4140
+ } = this;
4141
+ if (!instanceName) {
4142
+ // @ts-expect-error
4143
+ const instanceId = globalThis.__primitiveInstanceId++;
4144
+ // @ts-expect-error
4145
+ this.instanceName = `${klassName}.${methodName}:${instanceId} - ${new Error().stack?.split('\n')[2]}`;
4146
+ }
4147
+ const instanceLogName = `${logName} (${instanceName})`;
4148
+ // @ts-expect-error
4149
+ // eslint-disable-next-line
4150
+ globalThis.__WarpDriveMetricCountData[instanceLogName] =
4151
+ // @ts-expect-error
4152
+ // eslint-disable-next-line
4153
+ (globalThis.__WarpDriveMetricCountData[instanceLogName] || 0) + 1;
4154
+ // eslint-disable-next-line
4155
+ return original.apply(this, args);
4156
+ };
4157
+ }
4158
+ }
4159
+ interceptAndLog('Set', 'constructor');
4160
+ interceptAndLog('Set', 'add');
4161
+ interceptAndLog('Set', 'delete');
4162
+ interceptAndLog('Set', 'has');
4163
+ interceptAndLog('Set', 'set');
4164
+ interceptAndLog('Set', 'get');
4165
+ interceptAndLog('Map', 'constructor');
4166
+ interceptAndLog('Map', 'set');
4167
+ interceptAndLog('Map', 'delete');
4168
+ interceptAndLog('Map', 'has');
4169
+ interceptAndLog('Map', 'add');
4170
+ interceptAndLog('Map', 'get');
4171
+ interceptAndLog('WeakSet', 'constructor');
4172
+ interceptAndLog('WeakSet', 'add');
4173
+ interceptAndLog('WeakSet', 'delete');
4174
+ interceptAndLog('WeakSet', 'has');
4175
+ interceptAndLog('WeakSet', 'set');
4176
+ interceptAndLog('WeakSet', 'get');
4177
+ interceptAndLog('WeakMap', 'constructor');
4178
+ interceptAndLog('WeakMap', 'set');
4179
+ interceptAndLog('WeakMap', 'delete');
4180
+ interceptAndLog('WeakMap', 'has');
4181
+ interceptAndLog('WeakMap', 'add');
4182
+ interceptAndLog('WeakMap', 'get');
4183
+ }
4184
+ }
4185
+ }
4186
+ }
4187
+
3598
4188
  // `AwaitedKeys` is needed here to resolve any promise types like `PromiseBelongsTo`.
3599
4189
 
3600
4190
  /**
@@ -3639,7 +4229,8 @@ const EmptyClass = class {
3639
4229
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
3640
4230
  constructor(args) {}
3641
4231
  };
3642
- const BaseClass = macroCondition(dependencySatisfies('ember-source', '*')) ? macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_STORE_EXTENDS_EMBER_OBJECT) ? importSync('@ember/object') : EmptyClass : EmptyClass;
4232
+ const _BaseClass = macroCondition(dependencySatisfies('ember-source', '*')) ? macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_STORE_EXTENDS_EMBER_OBJECT) ? importSync('@ember/object') : EmptyClass : EmptyClass;
4233
+ const BaseClass = _BaseClass.default ? _BaseClass.default : _BaseClass;
3643
4234
  if (BaseClass !== EmptyClass) {
3644
4235
  deprecate(`The Store class extending from EmberObject is deprecated.
3645
4236
  Please remove usage of EmberObject APIs and mark your class as not requiring it.
@@ -3660,8 +4251,9 @@ const app = new EmberApp(defaults, {
3660
4251
  id: 'ember-data:deprecate-store-extends-ember-object',
3661
4252
  until: '6.0',
3662
4253
  for: 'ember-data',
4254
+ url: 'https://deprecations.emberjs.com/id/ember-data-deprecate-store-extends-ember-object',
3663
4255
  since: {
3664
- available: '5.4',
4256
+ available: '4.13',
3665
4257
  enabled: '5.4'
3666
4258
  }
3667
4259
  });
@@ -3721,12 +4313,9 @@ class Store extends BaseClass {
3721
4313
  * import Fetch from '@ember-data/request/fetch';
3722
4314
  *
3723
4315
  * class extends Store {
3724
- * constructor() {
3725
- * super(...arguments);
3726
- * this.requestManager = new RequestManager();
3727
- * this.requestManager.use([Fetch]);
3728
- * this.requestManager.useCache(CacheHandler);
3729
- * }
4316
+ * requestManager = new RequestManager()
4317
+ * .use([Fetch])
4318
+ * .useCache(CacheHandler);
3730
4319
  * }
3731
4320
  * ```
3732
4321
  *
@@ -3766,6 +4355,17 @@ class Store extends BaseClass {
3766
4355
 
3767
4356
  // Private
3768
4357
 
4358
+ /**
4359
+ * Async flush buffers notifications until flushed
4360
+ * by finalization of a future configured by store.request
4361
+ *
4362
+ * This is useful for ensuring that notifications are delivered
4363
+ * prior to the promise resolving but without risk of promise
4364
+ * interleaving.
4365
+ *
4366
+ * @internal
4367
+ */
4368
+
3769
4369
  // DEBUG-only properties
3770
4370
 
3771
4371
  get isDestroying() {
@@ -3799,7 +4399,6 @@ class Store extends BaseClass {
3799
4399
  // private
3800
4400
  this._requestCache = new RequestStateService(this);
3801
4401
  this._instanceCache = new InstanceCache(this);
3802
- this._documentCache = new Map();
3803
4402
  this.isDestroying = false;
3804
4403
  this.isDestroyed = false;
3805
4404
  }
@@ -3839,6 +4438,16 @@ class Store extends BaseClass {
3839
4438
  this._cbs = null;
3840
4439
  }
3841
4440
  }
4441
+
4442
+ /**
4443
+ * Executes the callback, ensurng that any work that calls
4444
+ * store._schedule is executed after in the right order.
4445
+ *
4446
+ * When queues already exist, scheduled callbacks will
4447
+ * join the existing queue.
4448
+ *
4449
+ * @internal
4450
+ */
3842
4451
  _join(cb) {
3843
4452
  if (this._cbs) {
3844
4453
  cb();
@@ -3947,7 +4556,7 @@ class Store extends BaseClass {
3947
4556
  // the user has had the chance to set the prop.
3948
4557
  const opts = {
3949
4558
  store: this,
3950
- [EnableHydration]: true
4559
+ [EnableHydration]: requestConfig[EnableHydration] ?? true
3951
4560
  };
3952
4561
  if (requestConfig.records) {
3953
4562
  const identifierCache = this.identifierCache;
@@ -3958,22 +4567,26 @@ class Store extends BaseClass {
3958
4567
  opts.disableTestWaiter = typeof requestConfig.disableTestWaiter === 'boolean' ? requestConfig.disableTestWaiter : true;
3959
4568
  }
3960
4569
  }
3961
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_REQUESTS)) {
3962
- let options;
3963
- try {
3964
- options = JSON.parse(JSON.stringify(requestConfig));
3965
- } catch {
3966
- options = requestConfig;
4570
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_REQUESTS)) {
4571
+ if (getGlobalConfig().WarpDrive.debug.LOG_REQUESTS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_REQUESTS) {
4572
+ let options;
4573
+ try {
4574
+ options = JSON.parse(JSON.stringify(requestConfig));
4575
+ } catch {
4576
+ options = requestConfig;
4577
+ }
4578
+ // eslint-disable-next-line no-console
4579
+ console.log(`request: [[START]] ${requestConfig.op && !requestConfig.url ? '(LEGACY) ' : ''}${requestConfig.op || '<unknown operation>'} ${requestConfig.url || '<empty url>'} ${requestConfig.method || '<empty method>'}`, options);
3967
4580
  }
3968
- // eslint-disable-next-line no-console
3969
- console.log(`request: [[START]] ${requestConfig.op && !requestConfig.url ? '(LEGACY) ' : ''}${requestConfig.op || '<unknown operation>'} ${requestConfig.url || '<empty url>'} ${requestConfig.method || '<empty method>'}`, options);
3970
4581
  }
3971
4582
  const request = Object.assign({}, requestConfig, opts);
3972
4583
  const future = this.requestManager.request(request);
3973
4584
  future.onFinalize(() => {
3974
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_REQUESTS)) {
3975
- // eslint-disable-next-line no-console
3976
- console.log(`request: [[FINALIZE]] ${requestConfig.op && !requestConfig.url ? '(LEGACY) ' : ''}${requestConfig.op || '<unknown operation>'} ${requestConfig.url || '<empty url>'} ${requestConfig.method || '<empty method>'}`);
4585
+ if (macroCondition(getGlobalConfig().WarpDrive.activeLogging.LOG_REQUESTS)) {
4586
+ if (getGlobalConfig().WarpDrive.debug.LOG_REQUESTS || globalThis.getWarpDriveRuntimeConfig().debug.LOG_REQUESTS) {
4587
+ // eslint-disable-next-line no-console
4588
+ console.log(`request: [[FINALIZE]] ${requestConfig.op && !requestConfig.url ? '(LEGACY) ' : ''}${requestConfig.op || '<unknown operation>'} ${requestConfig.url || '<empty url>'} ${requestConfig.method || '<empty method>'}`);
4589
+ }
3977
4590
  }
3978
4591
  // skip flush for legacy belongsTo
3979
4592
  if (requestConfig.op === 'findBelongsTo' && !requestConfig.url) {
@@ -4179,9 +4792,8 @@ class Store extends BaseClass {
4179
4792
  This will cause the record to be destroyed and freed up for garbage collection.
4180
4793
  Example
4181
4794
  ```javascript
4182
- store.findRecord('post', '1').then(function(post) {
4183
- store.unloadRecord(post);
4184
- });
4795
+ const { content: { data: post } } = await store.request(findRecord({ type: 'post', id: '1' }));
4796
+ store.unloadRecord(post);
4185
4797
  ```
4186
4798
  @method unloadRecord
4187
4799
  @public
@@ -4402,7 +5014,7 @@ class Store extends BaseClass {
4402
5014
  ```app/routes/post.js
4403
5015
  export default class PostRoute extends Route {
4404
5016
  model(params) {
4405
- return this.store.findRecord('post', params.post_id, { include: 'comments' });
5017
+ return this.store.findRecord('post', params.post_id, { include: ['comments'] });
4406
5018
  }
4407
5019
  }
4408
5020
  ```
@@ -4424,13 +5036,13 @@ class Store extends BaseClass {
4424
5036
  In this case, the post's comments would then be available in your template as
4425
5037
  `model.comments`.
4426
5038
  Multiple relationships can be requested using an `include` parameter consisting of a
4427
- comma-separated list (without white-space) while nested relationships can be specified
5039
+ list of relationship names, while nested relationships can be specified
4428
5040
  using a dot-separated sequence of relationship names. So to request both the post's
4429
5041
  comments and the authors of those comments the request would look like this:
4430
5042
  ```app/routes/post.js
4431
5043
  export default class PostRoute extends Route {
4432
5044
  model(params) {
4433
- return this.store.findRecord('post', params.post_id, { include: 'comments,comments.author' });
5045
+ return this.store.findRecord('post', params.post_id, { include: ['comments','comments.author'] });
4434
5046
  }
4435
5047
  }
4436
5048
  ```
@@ -4951,18 +5563,18 @@ class Store extends BaseClass {
4951
5563
  ```app/routes/posts.js
4952
5564
  export default class PostsRoute extends Route {
4953
5565
  model() {
4954
- return this.store.findAll('post', { include: 'comments' });
5566
+ return this.store.findAll('post', { include: ['comments'] });
4955
5567
  }
4956
5568
  }
4957
5569
  ```
4958
5570
  Multiple relationships can be requested using an `include` parameter consisting of a
4959
- comma-separated list (without white-space) while nested relationships can be specified
5571
+ list or relationship names, while nested relationships can be specified
4960
5572
  using a dot-separated sequence of relationship names. So to request both the posts'
4961
5573
  comments and the authors of those comments the request would look like this:
4962
5574
  ```app/routes/posts.js
4963
5575
  export default class PostsRoute extends Route {
4964
5576
  model() {
4965
- return this.store.findAll('post', { include: 'comments,comments.author' });
5577
+ return this.store.findAll('post', { include: ['comments','comments.author'] });
4966
5578
  }
4967
5579
  }
4968
5580
  ```
@@ -5233,16 +5845,6 @@ class Store extends BaseClass {
5233
5845
  if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
5234
5846
  assertDestroyingStore(this, '_push');
5235
5847
  }
5236
- if (macroCondition(getGlobalConfig().WarpDrive.debug.LOG_PAYLOADS)) {
5237
- try {
5238
- const data = JSON.parse(JSON.stringify(jsonApiDoc));
5239
- // eslint-disable-next-line no-console
5240
- console.log('EmberData | Payload - push', data);
5241
- } catch (e) {
5242
- // eslint-disable-next-line no-console
5243
- console.log('EmberData | Payload - push', jsonApiDoc);
5244
- }
5245
- }
5246
5848
  if (asyncFlush) {
5247
5849
  this._enableAsyncFlush = true;
5248
5850
  }
@@ -5373,10 +5975,10 @@ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.ENABLE_LEGACY_SCHEMA
5373
5975
  })(this._schema) : {};
5374
5976
  deprecate(`Use \`store.schema\` instead of \`store.getSchemaDefinitionService()\``, false, {
5375
5977
  id: 'ember-data:schema-service-updates',
5376
- until: '5.0',
5978
+ until: '6.0',
5377
5979
  for: 'ember-data',
5378
5980
  since: {
5379
- available: '5.4',
5981
+ available: '4.13',
5380
5982
  enabled: '5.4'
5381
5983
  }
5382
5984
  });
@@ -5385,10 +5987,10 @@ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.ENABLE_LEGACY_SCHEMA
5385
5987
  Store.prototype.registerSchemaDefinitionService = function (schema) {
5386
5988
  deprecate(`Use \`store.createSchemaService\` instead of \`store.registerSchemaDefinitionService()\``, false, {
5387
5989
  id: 'ember-data:schema-service-updates',
5388
- until: '5.0',
5990
+ until: '6.0',
5389
5991
  for: 'ember-data',
5390
5992
  since: {
5391
- available: '5.4',
5993
+ available: '4.13',
5392
5994
  enabled: '5.4'
5393
5995
  }
5394
5996
  });
@@ -5397,10 +5999,10 @@ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.ENABLE_LEGACY_SCHEMA
5397
5999
  Store.prototype.registerSchema = function (schema) {
5398
6000
  deprecate(`Use \`store.createSchemaService\` instead of \`store.registerSchema()\``, false, {
5399
6001
  id: 'ember-data:schema-service-updates',
5400
- until: '5.0',
6002
+ until: '6.0',
5401
6003
  for: 'ember-data',
5402
6004
  since: {
5403
- available: '5.4',
6005
+ available: '4.13',
5404
6006
  enabled: '5.4'
5405
6007
  }
5406
6008
  });
@@ -5463,9 +6065,9 @@ function normalizeProperties(store, identifier, properties) {
5463
6065
  if (macroCondition(getGlobalConfig().WarpDrive.env.DEBUG)) {
5464
6066
  assertRecordsPassedToHasMany(properties[prop]);
5465
6067
  }
5466
- properties[prop] = extractIdentifiersFromRecords(properties[prop]);
6068
+ properties[prop] = extractIdentifiersFromRecords$1(properties[prop]);
5467
6069
  } else if (field.kind === 'belongsTo') {
5468
- properties[prop] = extractIdentifierFromRecord(properties[prop]);
6070
+ properties[prop] = extractIdentifierFromRecord$1(properties[prop]);
5469
6071
  }
5470
6072
  }
5471
6073
  }
@@ -5493,250 +6095,208 @@ function assertRecordsPassedToHasMany(records) {
5493
6095
  });
5494
6096
  }()) : {};
5495
6097
  }
5496
- function extractIdentifiersFromRecords(records) {
5497
- return records.map(record => extractIdentifierFromRecord(record));
6098
+ function extractIdentifiersFromRecords$1(records) {
6099
+ return records.map(record => extractIdentifierFromRecord$1(record));
5498
6100
  }
5499
- function extractIdentifierFromRecord(recordOrPromiseRecord) {
6101
+ function extractIdentifierFromRecord$1(recordOrPromiseRecord) {
5500
6102
  if (!recordOrPromiseRecord) {
5501
6103
  return null;
5502
6104
  }
5503
6105
  const extract = recordIdentifierFor;
5504
6106
  return extract(recordOrPromiseRecord);
5505
6107
  }
6108
+ const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
6109
+ function calcShouldFetch(store, request, hasCachedValue, identifier) {
6110
+ const {
6111
+ cacheOptions
6112
+ } = request;
6113
+ return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
6114
+ }
6115
+ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
6116
+ const {
6117
+ cacheOptions
6118
+ } = request;
6119
+ return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
6120
+ }
6121
+ function isMutation(request) {
6122
+ return Boolean(request.op && MUTATION_OPS.has(request.op));
6123
+ }
6124
+ function isCacheAffecting(document) {
6125
+ if (!isMutation(document.request)) {
6126
+ return true;
6127
+ }
6128
+ // a mutation combined with a 204 has no cache impact when no known records were involved
6129
+ // a createRecord with a 201 with an empty response and no known records should similarly
6130
+ // have no cache impact
6131
+
6132
+ if (document.request.op === 'createRecord' && document.response?.status === 201) {
6133
+ return document.content ? Object.keys(document.content).length > 0 : false;
6134
+ }
6135
+ return document.response?.status !== 204;
6136
+ }
6137
+ function isAggregateError(error) {
6138
+ return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
6139
+ }
6140
+ // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
6141
+ function cloneError(error) {
6142
+ const isAggregate = isAggregateError(error);
6143
+ const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
6144
+ cloned.stack = error.stack;
6145
+ cloned.error = error.error;
6146
+
6147
+ // copy over enumerable properties
6148
+ Object.assign(cloned, error);
6149
+ return cloned;
6150
+ }
6151
+ function getPriority(identifier, deduped, priority) {
6152
+ if (identifier) {
6153
+ const existing = deduped.get(identifier);
6154
+ if (existing) {
6155
+ return existing.priority;
6156
+ }
6157
+ }
6158
+ return priority;
6159
+ }
5506
6160
 
5507
6161
  /**
5508
6162
  * @module @ember-data/store
5509
6163
  */
5510
- function urlFromLink(link) {
5511
- if (typeof link === 'string') return link;
5512
- return link.href;
5513
- }
5514
6164
 
5515
6165
  /**
5516
- * A Document is a class that wraps the response content from a request to the API
5517
- * returned by `Cache.put` or `Cache.peek`, converting resource-identifiers into
5518
- * record instances.
6166
+ * A CacheHandler that adds support for using an EmberData Cache with a RequestManager.
5519
6167
  *
5520
- * It is not directly instantiated by the user, and its properties should not
5521
- * be directly modified. Whether individual properties are mutable or not is
5522
- * determined by the record instance itself.
6168
+ * This handler will only run when a request has supplied a `store` instance. Requests
6169
+ * issued by the store via `store.request()` will automatically have the `store` instance
6170
+ * attached to the request.
5523
6171
  *
5524
- * @public
5525
- * @class Document
6172
+ * ```ts
6173
+ * requestManager.request({
6174
+ * store: store,
6175
+ * url: '/api/posts',
6176
+ * method: 'GET'
6177
+ * });
6178
+ * ```
6179
+ *
6180
+ * When this handler elects to handle a request, it will return the raw `StructuredDocument`
6181
+ * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
6182
+ * return a `Document` instance that will automatically update the UI when the cache is updated
6183
+ * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
6184
+ *
6185
+ * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
6186
+ * means that if desired you can issue requests that utilize the cache without needing to also
6187
+ * utilize Record instances if desired.
6188
+ *
6189
+ * Said differently, you could elect to issue all requests via a RequestManager, without ever using
6190
+ * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
6191
+ * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
6192
+ * approach of EmberData allows for this flexibility.
6193
+ *
6194
+ * ```ts
6195
+ * import { EnableHydration } from '@warp-drive/core-types/request';
6196
+ *
6197
+ * requestManager.request({
6198
+ * store: store,
6199
+ * url: '/api/posts',
6200
+ * method: 'GET',
6201
+ * [EnableHydration]: true
6202
+ * });
6203
+ *
6204
+ * @typedoc
5526
6205
  */
5527
- class Document {
5528
- /**
5529
- * The links object for this document, if any
5530
- *
5531
- * e.g.
5532
- *
5533
- * ```
5534
- * {
5535
- * self: '/articles?page[number]=3',
5536
- * }
5537
- * ```
5538
- *
5539
- * @property links
5540
- * @type {object|undefined} - a links object
5541
- * @public
5542
- */
5543
-
5544
- /**
5545
- * The primary data for this document, if any.
5546
- *
5547
- * If this document has no primary data (e.g. because it is an error document)
5548
- * this property will be `undefined`.
5549
- *
5550
- * For collections this will be an array of record instances,
5551
- * for single resource requests it will be a single record instance or null.
5552
- *
5553
- * @property data
5554
- * @public
5555
- * @type {object|Array<object>|null|undefined} - a data object
5556
- */
5557
-
5558
- /**
5559
- * The errors returned by the API for this request, if any
5560
- *
5561
- * @property errors
5562
- * @public
5563
- * @type {object|undefined} - an errors object
5564
- */
5565
-
5566
- /**
5567
- * The meta object for this document, if any
5568
- *
5569
- * @property meta
5570
- * @public
5571
- * @type {object|undefined} - a meta object
5572
- */
6206
+ const CacheHandler = {
6207
+ request(context, next) {
6208
+ // if we have no cache or no cache-key skip cache handling
6209
+ if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
6210
+ return next(context.request);
6211
+ }
6212
+ const {
6213
+ store
6214
+ } = context.request;
6215
+ const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
6216
+ if (identifier) {
6217
+ context.setIdentifier(identifier);
6218
+ }
5573
6219
 
5574
- /**
5575
- * The identifier associated with this document, if any
5576
- *
5577
- * @property identifier
5578
- * @public
5579
- * @type {StableDocumentIdentifier|null}
5580
- */
6220
+ // used to dedupe existing requests that match
6221
+ const DEDUPE = store.requestManager._deduped;
6222
+ const activeRequest = identifier && DEDUPE.get(identifier);
6223
+ const peeked = identifier ? store.cache.peekRequest(identifier) : null;
5581
6224
 
5582
- #store;
5583
- constructor(store, identifier) {
5584
- this.#store = store;
5585
- this.identifier = identifier;
5586
- }
5587
- async #request(link, options) {
5588
- const href = this.links?.[link];
5589
- if (!href) {
5590
- return null;
6225
+ // determine if we should skip cache
6226
+ if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
6227
+ if (activeRequest) {
6228
+ activeRequest.priority = {
6229
+ blocking: true
6230
+ };
6231
+ return activeRequest.promise;
6232
+ }
6233
+ let promise = fetchContentAndHydrate(next, context, identifier, {
6234
+ blocking: true
6235
+ });
6236
+ if (identifier) {
6237
+ promise = promise.finally(() => {
6238
+ DEDUPE.delete(identifier);
6239
+ store.notifications.notify(identifier, 'state');
6240
+ });
6241
+ DEDUPE.set(identifier, {
6242
+ priority: {
6243
+ blocking: true
6244
+ },
6245
+ promise
6246
+ });
6247
+ store.notifications.notify(identifier, 'state');
6248
+ }
6249
+ return promise;
5591
6250
  }
5592
- options.method = options.method || 'GET';
5593
- Object.assign(options, {
5594
- url: urlFromLink(href)
5595
- });
5596
- const response = await this.#store.request(options);
5597
- return response.content;
5598
- }
5599
6251
 
5600
- /**
5601
- * Fetches the related link for this document, returning a promise that resolves
5602
- * with the document when the request completes. If no related link is present,
5603
- * will fallback to the self link if present
5604
- *
5605
- * @method fetch
5606
- * @public
5607
- * @param {object} options
5608
- * @return Promise<Document>
5609
- */
5610
- fetch(options = {}) {
6252
+ // if we have not skipped cache, determine if we should update behind the scenes
6253
+ if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
6254
+ let promise = activeRequest?.promise || fetchContentAndHydrate(next, context, identifier, {
6255
+ blocking: false
6256
+ });
6257
+ if (identifier && !activeRequest) {
6258
+ promise = promise.finally(() => {
6259
+ DEDUPE.delete(identifier);
6260
+ store.notifications.notify(identifier, 'state');
6261
+ });
6262
+ DEDUPE.set(identifier, {
6263
+ priority: {
6264
+ blocking: false
6265
+ },
6266
+ promise
6267
+ });
6268
+ store.notifications.notify(identifier, 'state');
6269
+ }
6270
+ store.requestManager._pending.set(context.id, promise);
6271
+ }
5611
6272
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
5612
6273
  if (!test) {
5613
- throw new Error(`No self or related link`);
6274
+ throw new Error(`Expected a peeked request to be present`);
5614
6275
  }
5615
- })(this.links?.related || this.links?.self) : {};
5616
- options.cacheOptions = options.cacheOptions || {};
5617
- options.cacheOptions.key = this.identifier?.lid;
5618
- return this.#request(this.links.related ? 'related' : 'self', options);
5619
- }
5620
-
5621
- /**
5622
- * Fetches the next link for this document, returning a promise that resolves
5623
- * with the new document when the request completes, or null if there is no
5624
- * next link.
5625
- *
5626
- * @method next
5627
- * @public
5628
- * @param {object} options
5629
- * @return Promise<Document | null>
5630
- */
5631
- next(options = {}) {
5632
- return this.#request('next', options);
5633
- }
5634
-
5635
- /**
5636
- * Fetches the prev link for this document, returning a promise that resolves
5637
- * with the new document when the request completes, or null if there is no
5638
- * prev link.
5639
- *
5640
- * @method prev
5641
- * @public
5642
- * @param {object} options
5643
- * @return Promise<Document | null>
5644
- */
5645
- prev(options = {}) {
5646
- return this.#request('prev', options);
5647
- }
5648
-
5649
- /**
5650
- * Fetches the first link for this document, returning a promise that resolves
5651
- * with the new document when the request completes, or null if there is no
5652
- * first link.
5653
- *
5654
- * @method first
5655
- * @public
5656
- * @param {object} options
5657
- * @return Promise<Document | null>
5658
- */
5659
- first(options = {}) {
5660
- return this.#request('first', options);
5661
- }
5662
-
5663
- /**
5664
- * Fetches the last link for this document, returning a promise that resolves
5665
- * with the new document when the request completes, or null if there is no
5666
- * last link.
5667
- *
5668
- * @method last
5669
- * @public
5670
- * @param {object} options
5671
- * @return Promise<Document | null>
5672
- */
5673
- last(options = {}) {
5674
- return this.#request('last', options);
5675
- }
5676
-
5677
- /**
5678
- * Implemented for `JSON.stringify` support.
5679
- *
5680
- * Returns the JSON representation of the document wrapper.
5681
- *
5682
- * This is a shallow serialization, it does not deeply serialize
5683
- * the document's contents, leaving that to the individual record
5684
- * instances to determine how to do, if at all.
5685
- *
5686
- * @method toJSON
5687
- * @public
5688
- * @return
5689
- */
5690
- toJSON() {
5691
- const data = {};
5692
- data.identifier = this.identifier;
5693
- if (this.data !== undefined) {
5694
- data.data = this.data;
5695
- }
5696
- if (this.links !== undefined) {
5697
- data.links = this.links;
5698
- }
5699
- if (this.errors !== undefined) {
5700
- data.errors = this.errors;
5701
- }
5702
- if (this.meta !== undefined) {
5703
- data.meta = this.meta;
6276
+ })(peeked) : {};
6277
+ const shouldHydrate = context.request[EnableHydration] || false;
6278
+ context.setResponse(peeked.response);
6279
+ if ('error' in peeked) {
6280
+ const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6281
+ shouldHydrate,
6282
+ identifier
6283
+ }, peeked.content) : peeked.content;
6284
+ const newError = cloneError(peeked);
6285
+ newError.content = content;
6286
+ throw newError;
5704
6287
  }
5705
- return data;
6288
+ const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6289
+ shouldHydrate,
6290
+ identifier
6291
+ }, peeked.content) : peeked.content;
6292
+ return result;
5706
6293
  }
5707
- }
5708
- defineSignal(Document.prototype, 'data');
5709
- defineSignal(Document.prototype, 'links');
5710
- defineSignal(Document.prototype, 'errors');
5711
- defineSignal(Document.prototype, 'meta');
5712
-
5713
- /**
5714
- * @module @ember-data/store
5715
- */
5716
-
5717
- /**
5718
- * A service which an application may provide to the store via
5719
- * the store's `lifetimes` property to configure the behavior
5720
- * of the CacheHandler.
5721
- *
5722
- * The default behavior for request lifetimes is to never expire
5723
- * unless manually refreshed via `cacheOptions.reload` or `cacheOptions.backgroundReload`.
5724
- *
5725
- * Implementing this service allows you to programatically define
5726
- * when a request should be considered expired.
5727
- *
5728
- * @class <Interface> CachePolicy
5729
- * @public
5730
- */
5731
- const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
5732
- function isErrorDocument(document) {
5733
- return 'errors' in document;
5734
- }
5735
- function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
6294
+ };
6295
+ function maybeUpdateUiObjects(store, request, options, document) {
5736
6296
  const {
5737
6297
  identifier
5738
6298
  } = options;
5739
- if (!document) {
6299
+ if (!document || !options.shouldHydrate) {
5740
6300
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
5741
6301
  if (!test) {
5742
6302
  throw new Error(`The CacheHandler expected response content but none was found`);
@@ -5744,113 +6304,105 @@ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
5744
6304
  })(!options.shouldHydrate) : {};
5745
6305
  return document;
5746
6306
  }
5747
- if (isErrorDocument(document)) {
5748
- if (!identifier && !options.shouldHydrate) {
5749
- return document;
5750
- }
5751
- let doc;
5752
- if (identifier) {
5753
- doc = store._documentCache.get(identifier);
5754
- }
5755
- if (!doc) {
5756
- doc = new Document(store, identifier);
5757
- copyDocumentProperties(doc, document);
5758
- if (identifier) {
5759
- store._documentCache.set(identifier, doc);
5760
- }
5761
- } else if (!isFromCache) {
5762
- doc.data = undefined;
5763
- copyDocumentProperties(doc, document);
5764
- }
5765
- return options.shouldHydrate ? doc : document;
6307
+ if (identifier) {
6308
+ return store._instanceCache.getDocument(identifier);
5766
6309
  }
5767
- if (Array.isArray(document.data)) {
5768
- const {
5769
- recordArrayManager
5770
- } = store;
5771
- if (!identifier) {
5772
- if (!options.shouldHydrate) {
5773
- return document;
5774
- }
5775
- const data = recordArrayManager.createArray({
5776
- type: request.url,
5777
- identifiers: document.data,
5778
- doc: document,
5779
- query: request
5780
- });
5781
- const doc = new Document(store, null);
5782
- doc.data = data;
5783
- doc.meta = document.meta;
5784
- doc.links = document.links;
5785
- return doc;
5786
- }
5787
- let managed = recordArrayManager._keyedArrays.get(identifier.lid);
5788
- if (!managed) {
5789
- managed = recordArrayManager.createArray({
5790
- type: identifier.lid,
5791
- identifiers: document.data,
5792
- doc: document
5793
- });
5794
- recordArrayManager._keyedArrays.set(identifier.lid, managed);
5795
- const doc = new Document(store, identifier);
5796
- doc.data = managed;
5797
- doc.meta = document.meta;
5798
- doc.links = document.links;
5799
- store._documentCache.set(identifier, doc);
5800
- return options.shouldHydrate ? doc : document;
5801
- } else {
5802
- const doc = store._documentCache.get(identifier);
5803
- if (!isFromCache) {
5804
- recordArrayManager.populateManagedArray(managed, document.data, document);
5805
- doc.data = managed;
5806
- doc.meta = document.meta;
5807
- doc.links = document.links;
5808
- }
5809
- return options.shouldHydrate ? doc : document;
6310
+
6311
+ // if we don't have an identifier, we give the document
6312
+ // its own local cache
6313
+ return new ReactiveDocument(store, null, {
6314
+ request,
6315
+ document
6316
+ });
6317
+ }
6318
+ function updateCacheForSuccess(store, request, options, document) {
6319
+ let response = null;
6320
+ if (isMutation(request)) {
6321
+ const record = request.data?.record || request.records?.[0];
6322
+ if (record) {
6323
+ response = store.cache.didCommit(record, document);
6324
+
6325
+ // a mutation combined with a 204 has no cache impact when no known records were involved
6326
+ // a createRecord with a 201 with an empty response and no known records should similarly
6327
+ // have no cache impact
6328
+ } else if (isCacheAffecting(document)) {
6329
+ response = store.cache.put(document);
5810
6330
  }
5811
6331
  } else {
5812
- if (!identifier && !options.shouldHydrate) {
5813
- return document;
5814
- }
5815
- const data = document.data ? store.peekRecord(document.data) : null;
5816
- let doc;
5817
- if (identifier) {
5818
- doc = store._documentCache.get(identifier);
5819
- }
5820
- if (!doc) {
5821
- doc = new Document(store, identifier);
5822
- doc.data = data;
5823
- copyDocumentProperties(doc, document);
5824
- if (identifier) {
5825
- store._documentCache.set(identifier, doc);
5826
- }
5827
- } else if (!isFromCache) {
5828
- doc.data = data;
5829
- copyDocumentProperties(doc, document);
5830
- }
5831
- return options.shouldHydrate ? doc : document;
6332
+ response = store.cache.put(document);
5832
6333
  }
6334
+ return maybeUpdateUiObjects(store, request, options, response);
5833
6335
  }
5834
- function calcShouldFetch(store, request, hasCachedValue, identifier) {
6336
+ function handleFetchSuccess(store, context, options, document) {
5835
6337
  const {
5836
- cacheOptions
5837
- } = request;
5838
- return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
6338
+ request
6339
+ } = context;
6340
+ store.requestManager._pending.delete(context.id);
6341
+ store._enableAsyncFlush = true;
6342
+ let response;
6343
+ store._join(() => {
6344
+ response = updateCacheForSuccess(store, request, options, document);
6345
+ });
6346
+ store._enableAsyncFlush = null;
6347
+ if (store.lifetimes?.didRequest) {
6348
+ store.lifetimes.didRequest(context.request, document.response, options.identifier, store);
6349
+ }
6350
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
6351
+ if (finalPriority.blocking) {
6352
+ return response;
6353
+ } else {
6354
+ store.notifications._flush();
6355
+ }
5839
6356
  }
5840
- function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
5841
- const {
5842
- cacheOptions
5843
- } = request;
5844
- return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
6357
+ function updateCacheForError(store, context, options, error) {
6358
+ let response;
6359
+ if (isMutation(context.request)) {
6360
+ // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
6361
+ // currently we let the response remain undefiend.
6362
+ const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
6363
+ const record = context.request.data?.record || context.request.records?.[0];
6364
+ store.cache.commitWasRejected(record, errors);
6365
+ } else {
6366
+ response = store.cache.put(error);
6367
+ return maybeUpdateUiObjects(store, context.request, options, response);
6368
+ }
5845
6369
  }
5846
- function isMutation(request) {
5847
- return Boolean(request.op && MUTATION_OPS.has(request.op));
6370
+ function handleFetchError(store, context, options, error) {
6371
+ store.requestManager._pending.delete(context.id);
6372
+ if (context.request.signal?.aborted) {
6373
+ throw error;
6374
+ }
6375
+ store._enableAsyncFlush = true;
6376
+ let response;
6377
+ store._join(() => {
6378
+ response = updateCacheForError(store, context, options, error);
6379
+ });
6380
+ store._enableAsyncFlush = null;
6381
+ if (options.identifier && store.lifetimes?.didRequest) {
6382
+ store.lifetimes.didRequest(context.request, error.response, options.identifier, store);
6383
+ }
6384
+ if (isMutation(context.request)) {
6385
+ throw error;
6386
+ }
6387
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
6388
+ if (finalPriority.blocking) {
6389
+ const newError = cloneError(error);
6390
+ newError.content = response;
6391
+ throw newError;
6392
+ } else {
6393
+ store.notifications._flush();
6394
+ }
5848
6395
  }
5849
- function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
6396
+ function fetchContentAndHydrate(next, context, identifier, priority) {
5850
6397
  const {
5851
6398
  store
5852
6399
  } = context.request;
5853
6400
  const shouldHydrate = context.request[EnableHydration] || false;
6401
+ const options = {
6402
+ shouldHydrate,
6403
+ identifier,
6404
+ priority
6405
+ };
5854
6406
  let isMut = false;
5855
6407
  if (isMutation(context.request)) {
5856
6408
  isMut = true;
@@ -5867,80 +6419,11 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
5867
6419
  }
5868
6420
  if (store.lifetimes?.willRequest) {
5869
6421
  store.lifetimes.willRequest(context.request, identifier, store);
5870
- }
5871
- const promise = next(context.request).then(document => {
5872
- store.requestManager._pending.delete(context.id);
5873
- store._enableAsyncFlush = true;
5874
- let response;
5875
- store._join(() => {
5876
- if (isMutation(context.request)) {
5877
- const record = context.request.data?.record || context.request.records?.[0];
5878
- if (record) {
5879
- response = store.cache.didCommit(record, document);
5880
-
5881
- // a mutation combined with a 204 has no cache impact when no known records were involved
5882
- // a createRecord with a 201 with an empty response and no known records should similarly
5883
- // have no cache impact
5884
- } else if (isCacheAffecting(document)) {
5885
- response = store.cache.put(document);
5886
- }
5887
- } else {
5888
- response = store.cache.put(document);
5889
- }
5890
- response = maybeUpdateUiObjects(store, context.request, {
5891
- shouldHydrate,
5892
- shouldFetch,
5893
- shouldBackgroundFetch,
5894
- identifier
5895
- }, response, false);
5896
- });
5897
- store._enableAsyncFlush = null;
5898
- if (store.lifetimes?.didRequest) {
5899
- store.lifetimes.didRequest(context.request, document.response, identifier, store);
5900
- }
5901
- if (shouldFetch) {
5902
- return response;
5903
- } else if (shouldBackgroundFetch) {
5904
- store.notifications._flush();
5905
- }
5906
- }, error => {
5907
- store.requestManager._pending.delete(context.id);
5908
- if (context.request.signal?.aborted) {
5909
- throw error;
5910
- }
5911
- store.requestManager._pending.delete(context.id);
5912
- store._enableAsyncFlush = true;
5913
- let response;
5914
- store._join(() => {
5915
- if (isMutation(context.request)) {
5916
- // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
5917
- // currently we let the response remain undefiend.
5918
- const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
5919
- const record = context.request.data?.record || context.request.records?.[0];
5920
- store.cache.commitWasRejected(record, errors);
5921
- // re-throw the original error to preserve `errors` property.
5922
- throw error;
5923
- } else {
5924
- response = store.cache.put(error);
5925
- response = maybeUpdateUiObjects(store, context.request, {
5926
- shouldHydrate,
5927
- shouldFetch,
5928
- shouldBackgroundFetch,
5929
- identifier
5930
- }, response, false);
5931
- }
5932
- });
5933
- store._enableAsyncFlush = null;
5934
- if (identifier && store.lifetimes?.didRequest) {
5935
- store.lifetimes.didRequest(context.request, error.response, identifier, store);
5936
- }
5937
- if (!shouldBackgroundFetch) {
5938
- const newError = cloneError(error);
5939
- newError.content = response;
5940
- throw newError;
5941
- } else {
5942
- store.notifications._flush();
5943
- }
6422
+ }
6423
+ const promise = next(context.request).then(document => {
6424
+ return handleFetchSuccess(store, context, options, document);
6425
+ }, error => {
6426
+ return handleFetchError(store, context, options, error);
5944
6427
  });
5945
6428
  if (!isMut) {
5946
6429
  return promise;
@@ -5962,129 +6445,475 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
5962
6445
  }]
5963
6446
  });
5964
6447
  }
5965
- function isAggregateError(error) {
5966
- return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
5967
- }
5968
- // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
5969
- function cloneError(error) {
5970
- const isAggregate = isAggregateError(error);
5971
- const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
5972
- cloned.stack = error.stack;
5973
- cloned.error = error.error;
5974
-
5975
- // copy over enumerable properties
5976
- Object.assign(cloned, error);
5977
- return cloned;
5978
- }
5979
6448
 
5980
6449
  /**
5981
- * A CacheHandler that adds support for using an EmberData Cache with a RequestManager.
5982
- *
5983
- * This handler will only run when a request has supplied a `store` instance. Requests
5984
- * issued by the store via `store.request()` will automatically have the `store` instance
5985
- * attached to the request.
5986
- *
5987
- * ```ts
5988
- * requestManager.request({
5989
- * store: store,
5990
- * url: '/api/posts',
5991
- * method: 'GET'
5992
- * });
5993
- * ```
5994
- *
5995
- * When this handler elects to handle a request, it will return the raw `StructuredDocument`
5996
- * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
5997
- * return a `Document` instance that will automatically update the UI when the cache is updated
5998
- * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
5999
- *
6000
- * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
6001
- * means that if desired you can issue requests that utilize the cache without needing to also
6002
- * utilize Record instances if desired.
6003
- *
6004
- * Said differently, you could elect to issue all requests via a RequestManager, without ever using
6005
- * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
6006
- * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
6007
- * approach of EmberData allows for this flexibility.
6008
- *
6009
- * ```ts
6010
- * import { EnableHydration } from '@warp-drive/core-types/request';
6011
- *
6012
- * requestManager.request({
6013
- * store: store,
6014
- * url: '/api/posts',
6015
- * method: 'GET',
6016
- * [EnableHydration]: true
6017
- * });
6018
- *
6019
- * @typedoc
6020
- */
6021
- const CacheHandler = {
6022
- request(context, next) {
6023
- // if we have no cache or no cache-key skip cache handling
6024
- if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
6025
- return next(context.request);
6026
- }
6027
- const {
6028
- store
6029
- } = context.request;
6030
- const identifier = store.identifierCache.getOrCreateDocumentIdentifier(context.request);
6031
- const peeked = identifier ? store.cache.peekRequest(identifier) : null;
6450
+ @module @ember-data/store
6451
+ */
6452
+ /**
6453
+ A `ManyArray` is a `MutableArray` that represents the contents of a has-many
6454
+ relationship.
6032
6455
 
6033
- // determine if we should skip cache
6034
- if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
6035
- return fetchContentAndHydrate(next, context, identifier, true, false);
6456
+ The `ManyArray` is instantiated lazily the first time the relationship is
6457
+ requested.
6458
+
6459
+ This class is not intended to be directly instantiated by consuming applications.
6460
+
6461
+ ### Inverses
6462
+
6463
+ Often, the relationships in Ember Data applications will have
6464
+ an inverse. For example, imagine the following models are
6465
+ defined:
6466
+
6467
+ ```app/models/post.js
6468
+ import Model, { hasMany } from '@ember-data/model';
6469
+
6470
+ export default class PostModel extends Model {
6471
+ @hasMany('comment') comments;
6472
+ }
6473
+ ```
6474
+
6475
+ ```app/models/comment.js
6476
+ import Model, { belongsTo } from '@ember-data/model';
6477
+
6478
+ export default class CommentModel extends Model {
6479
+ @belongsTo('post') post;
6480
+ }
6481
+ ```
6482
+
6483
+ If you created a new instance of `Post` and added
6484
+ a `Comment` record to its `comments` has-many
6485
+ relationship, you would expect the comment's `post`
6486
+ property to be set to the post that contained
6487
+ the has-many.
6488
+
6489
+ We call the record to which a relationship belongs-to the
6490
+ relationship's _owner_.
6491
+
6492
+ @class ManyArray
6493
+ @public
6494
+ */
6495
+ class RelatedCollection extends IdentifierArray {
6496
+ /**
6497
+ The loading state of this array
6498
+ @property {Boolean} isLoaded
6499
+ @public
6500
+ */
6501
+
6502
+ /**
6503
+ `true` if the relationship is polymorphic, `false` otherwise.
6504
+ @property {Boolean} isPolymorphic
6505
+ @private
6506
+ */
6507
+
6508
+ /**
6509
+ Metadata associated with the request for async hasMany relationships.
6510
+ Example
6511
+ Given that the server returns the following JSON payload when fetching a
6512
+ hasMany relationship:
6513
+ ```js
6514
+ {
6515
+ "comments": [{
6516
+ "id": 1,
6517
+ "comment": "This is the first comment",
6518
+ }, {
6519
+ // ...
6520
+ }],
6521
+ "meta": {
6522
+ "page": 1,
6523
+ "total": 5
6524
+ }
6036
6525
  }
6526
+ ```
6527
+ You can then access the meta data via the `meta` property:
6528
+ ```js
6529
+ let comments = await post.comments;
6530
+ let meta = comments.meta;
6531
+ // meta.page => 1
6532
+ // meta.total => 5
6533
+ ```
6534
+ @property {Object | null} meta
6535
+ @public
6536
+ */
6037
6537
 
6038
- // if we have not skipped cache, determine if we should update behind the scenes
6039
- if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
6040
- const promise = fetchContentAndHydrate(next, context, identifier, false, true);
6041
- store.requestManager._pending.set(context.id, promise);
6538
+ /**
6539
+ * Retrieve the links for this relationship
6540
+ *
6541
+ @property {Object | null} links
6542
+ @public
6543
+ */
6544
+
6545
+ constructor(options) {
6546
+ super(options);
6547
+ this.isLoaded = options.isLoaded || false;
6548
+ this.isAsync = options.isAsync || false;
6549
+ this.isPolymorphic = options.isPolymorphic || false;
6550
+ this.identifier = options.identifier;
6551
+ this.key = options.key;
6552
+ }
6553
+ [MUTATE](target, receiver, prop, args, _SIGNAL) {
6554
+ switch (prop) {
6555
+ case 'length 0':
6556
+ {
6557
+ Reflect.set(target, 'length', 0);
6558
+ mutateReplaceRelatedRecords(this, [], _SIGNAL);
6559
+ return true;
6560
+ }
6561
+ case 'replace cell':
6562
+ {
6563
+ const [index, prior, value] = args;
6564
+ target[index] = value;
6565
+ mutateReplaceRelatedRecord(this, {
6566
+ value,
6567
+ prior,
6568
+ index
6569
+ }, _SIGNAL);
6570
+ return true;
6571
+ }
6572
+ case 'push':
6573
+ {
6574
+ const newValues = extractIdentifiersFromRecords(args);
6575
+ assertNoDuplicates(this, target, currentState => currentState.push(...newValues), `Cannot push duplicates to a hasMany's state.`);
6576
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_MANY_ARRAY_DUPLICATES)) {
6577
+ // dedupe
6578
+ const seen = new Set(target);
6579
+ const unique = new Set();
6580
+ args.forEach(item => {
6581
+ const identifier = recordIdentifierFor(item);
6582
+ if (!seen.has(identifier)) {
6583
+ seen.add(identifier);
6584
+ unique.add(item);
6585
+ }
6586
+ });
6587
+ const newArgs = Array.from(unique);
6588
+ const result = Reflect.apply(target[prop], receiver, newArgs);
6589
+ if (newArgs.length) {
6590
+ mutateAddToRelatedRecords(this, {
6591
+ value: extractIdentifiersFromRecords(newArgs)
6592
+ }, _SIGNAL);
6593
+ }
6594
+ return result;
6595
+ }
6596
+
6597
+ // else, no dedupe, error on duplicates
6598
+ const result = Reflect.apply(target[prop], receiver, args);
6599
+ if (newValues.length) {
6600
+ mutateAddToRelatedRecords(this, {
6601
+ value: newValues
6602
+ }, _SIGNAL);
6603
+ }
6604
+ return result;
6605
+ }
6606
+ case 'pop':
6607
+ {
6608
+ const result = Reflect.apply(target[prop], receiver, args);
6609
+ if (result) {
6610
+ mutateRemoveFromRelatedRecords(this, {
6611
+ value: recordIdentifierFor(result)
6612
+ }, _SIGNAL);
6613
+ }
6614
+ return result;
6615
+ }
6616
+ case 'unshift':
6617
+ {
6618
+ const newValues = extractIdentifiersFromRecords(args);
6619
+ assertNoDuplicates(this, target, currentState => currentState.unshift(...newValues), `Cannot unshift duplicates to a hasMany's state.`);
6620
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_MANY_ARRAY_DUPLICATES)) {
6621
+ // dedupe
6622
+ const seen = new Set(target);
6623
+ const unique = new Set();
6624
+ args.forEach(item => {
6625
+ const identifier = recordIdentifierFor(item);
6626
+ if (!seen.has(identifier)) {
6627
+ seen.add(identifier);
6628
+ unique.add(item);
6629
+ }
6630
+ });
6631
+ const newArgs = Array.from(unique);
6632
+ const result = Reflect.apply(target[prop], receiver, newArgs);
6633
+ if (newArgs.length) {
6634
+ mutateAddToRelatedRecords(this, {
6635
+ value: extractIdentifiersFromRecords(newArgs),
6636
+ index: 0
6637
+ }, _SIGNAL);
6638
+ }
6639
+ return result;
6640
+ }
6641
+
6642
+ // else, no dedupe, error on duplicates
6643
+ const result = Reflect.apply(target[prop], receiver, args);
6644
+ if (newValues.length) {
6645
+ mutateAddToRelatedRecords(this, {
6646
+ value: newValues,
6647
+ index: 0
6648
+ }, _SIGNAL);
6649
+ }
6650
+ return result;
6651
+ }
6652
+ case 'shift':
6653
+ {
6654
+ const result = Reflect.apply(target[prop], receiver, args);
6655
+ if (result) {
6656
+ mutateRemoveFromRelatedRecords(this, {
6657
+ value: recordIdentifierFor(result),
6658
+ index: 0
6659
+ }, _SIGNAL);
6660
+ }
6661
+ return result;
6662
+ }
6663
+ case 'sort':
6664
+ {
6665
+ const result = Reflect.apply(target[prop], receiver, args);
6666
+ mutateSortRelatedRecords(this, result.map(recordIdentifierFor), _SIGNAL);
6667
+ return result;
6668
+ }
6669
+ case 'splice':
6670
+ {
6671
+ const [start, deleteCount, ...adds] = args;
6672
+
6673
+ // detect a full replace
6674
+ if (start === 0 && deleteCount === this[SOURCE].length) {
6675
+ const newValues = extractIdentifiersFromRecords(adds);
6676
+ assertNoDuplicates(this, target, currentState => currentState.splice(start, deleteCount, ...newValues), `Cannot replace a hasMany's state with a new state that contains duplicates.`);
6677
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_MANY_ARRAY_DUPLICATES)) {
6678
+ // dedupe
6679
+ const current = new Set(adds);
6680
+ const unique = Array.from(current);
6681
+ const newArgs = [start, deleteCount].concat(unique);
6682
+ const result = Reflect.apply(target[prop], receiver, newArgs);
6683
+ mutateReplaceRelatedRecords(this, extractIdentifiersFromRecords(unique), _SIGNAL);
6684
+ return result;
6685
+ }
6686
+
6687
+ // else, no dedupe, error on duplicates
6688
+ const result = Reflect.apply(target[prop], receiver, args);
6689
+ mutateReplaceRelatedRecords(this, newValues, _SIGNAL);
6690
+ return result;
6691
+ }
6692
+ const newValues = extractIdentifiersFromRecords(adds);
6693
+ assertNoDuplicates(this, target, currentState => currentState.splice(start, deleteCount, ...newValues), `Cannot splice a hasMany's state with a new state that contains duplicates.`);
6694
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_MANY_ARRAY_DUPLICATES)) {
6695
+ // dedupe
6696
+ const currentState = target.slice();
6697
+ currentState.splice(start, deleteCount);
6698
+ const seen = new Set(currentState);
6699
+ const unique = [];
6700
+ adds.forEach(item => {
6701
+ const identifier = recordIdentifierFor(item);
6702
+ if (!seen.has(identifier)) {
6703
+ seen.add(identifier);
6704
+ unique.push(item);
6705
+ }
6706
+ });
6707
+ const newArgs = [start, deleteCount, ...unique];
6708
+ const result = Reflect.apply(target[prop], receiver, newArgs);
6709
+ if (deleteCount > 0) {
6710
+ mutateRemoveFromRelatedRecords(this, {
6711
+ value: result.map(recordIdentifierFor),
6712
+ index: start
6713
+ }, _SIGNAL);
6714
+ }
6715
+ if (unique.length > 0) {
6716
+ mutateAddToRelatedRecords(this, {
6717
+ value: extractIdentifiersFromRecords(unique),
6718
+ index: start
6719
+ }, _SIGNAL);
6720
+ }
6721
+ return result;
6722
+ }
6723
+
6724
+ // else, no dedupe, error on duplicates
6725
+ const result = Reflect.apply(target[prop], receiver, args);
6726
+ if (deleteCount > 0) {
6727
+ mutateRemoveFromRelatedRecords(this, {
6728
+ value: result.map(recordIdentifierFor),
6729
+ index: start
6730
+ }, _SIGNAL);
6731
+ }
6732
+ if (newValues.length > 0) {
6733
+ mutateAddToRelatedRecords(this, {
6734
+ value: newValues,
6735
+ index: start
6736
+ }, _SIGNAL);
6737
+ }
6738
+ return result;
6739
+ }
6740
+ default:
6741
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6742
+ {
6743
+ throw new Error(`unable to convert ${prop} into a transaction that updates the cache state for this record array`);
6744
+ }
6745
+ })() : {};
6042
6746
  }
6747
+ }
6748
+ notify() {
6749
+ const signal = this[ARRAY_SIGNAL];
6750
+ signal.shouldReset = true;
6751
+ notifyArray(this);
6752
+ }
6753
+
6754
+ /**
6755
+ Reloads all of the records in the manyArray. If the manyArray
6756
+ holds a relationship that was originally fetched using a links url
6757
+ EmberData will revisit the original links url to repopulate the
6758
+ relationship.
6759
+ If the ManyArray holds the result of a `store.query()` reload will
6760
+ re-run the original query.
6761
+ Example
6762
+ ```javascript
6763
+ let user = store.peekRecord('user', '1')
6764
+ await login(user);
6765
+ let permissions = await user.permissions;
6766
+ await permissions.reload();
6767
+ ```
6768
+ @method reload
6769
+ @public
6770
+ */
6771
+ reload(options) {
6043
6772
  macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6044
6773
  if (!test) {
6045
- throw new Error(`Expected a peeked request to be present`);
6774
+ throw new Error(`Expected the manager for ManyArray to implement reloadHasMany`);
6046
6775
  }
6047
- })(peeked) : {};
6048
- const shouldHydrate = context.request[EnableHydration] || false;
6049
- context.setResponse(peeked.response);
6050
- if ('error' in peeked) {
6051
- const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6052
- shouldHydrate,
6053
- identifier
6054
- }, peeked.content, true) : peeked.content;
6055
- const newError = cloneError(peeked);
6056
- newError.content = content;
6057
- throw newError;
6058
- }
6059
- const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
6060
- shouldHydrate,
6061
- identifier
6062
- }, peeked.content, true) : peeked.content;
6063
- return result;
6776
+ })(typeof this._manager.reloadHasMany === 'function') : {};
6777
+ // TODO this is odd, we don't ask the store for anything else like this?
6778
+ return this._manager.reloadHasMany(this.key, options);
6064
6779
  }
6065
- };
6066
- function copyDocumentProperties(target, source) {
6067
- if ('links' in source) {
6068
- target.links = source.links;
6069
- }
6070
- if ('meta' in source) {
6071
- target.meta = source.meta;
6780
+
6781
+ /**
6782
+ Saves all of the records in the `ManyArray`.
6783
+ Note: this API can only be used in legacy mode with a configured Adapter.
6784
+ Example
6785
+ ```javascript
6786
+ const { content: { data: inbox } } = await store.request(findRecord({ type: 'inbox', id: '1' }));
6787
+ let messages = await inbox.messages;
6788
+ messages.forEach((message) => {
6789
+ message.isRead = true;
6790
+ });
6791
+ messages.save();
6792
+ ```
6793
+ @method save
6794
+ @public
6795
+ @return {PromiseArray} promise
6796
+ */
6797
+
6798
+ /**
6799
+ Create a child record within the owner
6800
+ @method createRecord
6801
+ @public
6802
+ @param {Object} hash
6803
+ @return {Model} record
6804
+ */
6805
+ createRecord(hash) {
6806
+ const {
6807
+ store
6808
+ } = this;
6809
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6810
+ if (!test) {
6811
+ throw new Error(`Expected modelName to be set`);
6812
+ }
6813
+ })(this.modelName) : {};
6814
+ const record = store.createRecord(this.modelName, hash);
6815
+ this.push(record);
6816
+ return record;
6072
6817
  }
6073
- if ('errors' in source) {
6074
- target.errors = source.errors;
6818
+ destroy() {
6819
+ super.destroy(false);
6075
6820
  }
6076
6821
  }
6077
- function isCacheAffecting(document) {
6078
- if (!isMutation(document.request)) {
6079
- return true;
6080
- }
6081
- // a mutation combined with a 204 has no cache impact when no known records were involved
6082
- // a createRecord with a 201 with an empty response and no known records should similarly
6083
- // have no cache impact
6084
-
6085
- if (document.request.op === 'createRecord' && document.response?.status === 201) {
6086
- return document.content ? Object.keys(document.content).length > 0 : false;
6822
+ RelatedCollection.prototype.isAsync = false;
6823
+ RelatedCollection.prototype.isPolymorphic = false;
6824
+ RelatedCollection.prototype.identifier = null;
6825
+ RelatedCollection.prototype.cache = null;
6826
+ RelatedCollection.prototype._inverseIsAsync = false;
6827
+ RelatedCollection.prototype.key = '';
6828
+ RelatedCollection.prototype.DEPRECATED_CLASS_NAME = 'ManyArray';
6829
+ function assertRecordPassedToHasMany(record) {
6830
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6831
+ if (!test) {
6832
+ throw new Error(`All elements of a hasMany relationship must be instances of Model, you passed ${typeof record}`);
6833
+ }
6834
+ })(function () {
6835
+ try {
6836
+ recordIdentifierFor(record);
6837
+ return true;
6838
+ } catch {
6839
+ return false;
6840
+ }
6841
+ }()) : {};
6842
+ }
6843
+ function extractIdentifiersFromRecords(records) {
6844
+ return records.map(extractIdentifierFromRecord);
6845
+ }
6846
+ function extractIdentifierFromRecord(recordOrPromiseRecord) {
6847
+ assertRecordPassedToHasMany(recordOrPromiseRecord);
6848
+ return recordIdentifierFor(recordOrPromiseRecord);
6849
+ }
6850
+ function assertNoDuplicates(collection, target, callback, reason) {
6851
+ const state = target.slice();
6852
+ callback(state);
6853
+ if (state.length !== new Set(state).size) {
6854
+ const duplicates = state.filter((currentValue, currentIndex) => state.indexOf(currentValue) !== currentIndex);
6855
+ if (macroCondition(getGlobalConfig().WarpDrive.deprecations.DEPRECATE_MANY_ARRAY_DUPLICATES)) {
6856
+ deprecate(`${reason} This behavior is deprecated. Found duplicates for the following records within the new state provided to \`<${collection.identifier.type}:${collection.identifier.id || collection.identifier.lid}>.${collection.key}\`\n\t- ${Array.from(new Set(duplicates)).map(r => isStableIdentifier(r) ? r.lid : recordIdentifierFor(r).lid).sort((a, b) => a.localeCompare(b)).join('\n\t- ')}`, false, {
6857
+ id: 'ember-data:deprecate-many-array-duplicates',
6858
+ for: 'ember-data',
6859
+ until: '6.0',
6860
+ since: {
6861
+ enabled: '5.3',
6862
+ available: '4.13'
6863
+ }
6864
+ });
6865
+ } else {
6866
+ throw new Error(`${reason} Found duplicates for the following records within the new state provided to \`<${collection.identifier.type}:${collection.identifier.id || collection.identifier.lid}>.${collection.key}\`\n\t- ${Array.from(new Set(duplicates)).map(r => isStableIdentifier(r) ? r.lid : recordIdentifierFor(r).lid).sort((a, b) => a.localeCompare(b)).join('\n\t- ')}`);
6867
+ }
6087
6868
  }
6088
- return document.response?.status !== 204;
6089
6869
  }
6090
- export { ARRAY_SIGNAL as A, CacheHandler as C, IdentifierArray as I, MUTATE as M, RecordArrayManager as R, Store as S, _clearCaches as _, setIdentifierGenerationMethod as a, setIdentifierUpdateMethod as b, setIdentifierForgetMethod as c, setIdentifierResetMethod as d, setKeyInfoForResource as e, constructResource as f, coerceId as g, ensureStringId as h, isStableIdentifier as i, Collection as j, SOURCE as k, fastPush as l, removeRecordDataFor as m, notifyArray as n, setRecordIdentifier as o, peekCache as p, StoreMap as q, recordIdentifierFor as r, storeFor as s, setCacheFor as t, normalizeModelName as u };
6870
+ function mutateAddToRelatedRecords(collection, operationInfo, _SIGNAL) {
6871
+ mutate(collection, {
6872
+ op: 'add',
6873
+ record: collection.identifier,
6874
+ field: collection.key,
6875
+ ...operationInfo
6876
+ }, _SIGNAL);
6877
+ }
6878
+ function mutateRemoveFromRelatedRecords(collection, operationInfo, _SIGNAL) {
6879
+ mutate(collection, {
6880
+ op: 'remove',
6881
+ record: collection.identifier,
6882
+ field: collection.key,
6883
+ ...operationInfo
6884
+ }, _SIGNAL);
6885
+ }
6886
+ function mutateReplaceRelatedRecord(collection, operationInfo, _SIGNAL) {
6887
+ mutate(collection, {
6888
+ op: 'replaceRelatedRecord',
6889
+ record: collection.identifier,
6890
+ field: collection.key,
6891
+ ...operationInfo
6892
+ }, _SIGNAL);
6893
+ }
6894
+ function mutateReplaceRelatedRecords(collection, value, _SIGNAL) {
6895
+ mutate(collection, {
6896
+ op: 'replaceRelatedRecords',
6897
+ record: collection.identifier,
6898
+ field: collection.key,
6899
+ value
6900
+ }, _SIGNAL);
6901
+ }
6902
+ function mutateSortRelatedRecords(collection, value, _SIGNAL) {
6903
+ mutate(collection, {
6904
+ op: 'sortRelatedRecords',
6905
+ record: collection.identifier,
6906
+ field: collection.key,
6907
+ value
6908
+ }, _SIGNAL);
6909
+ }
6910
+ function mutate(collection, mutation, _SIGNAL) {
6911
+ macroCondition(getGlobalConfig().WarpDrive.env.DEBUG) ? (test => {
6912
+ if (!test) {
6913
+ throw new Error(`Expected the manager for ManyArray to implement mutate`);
6914
+ }
6915
+ })(typeof collection._manager.mutate === 'function') : {};
6916
+ collection._manager.mutate(mutation);
6917
+ addToTransaction(_SIGNAL);
6918
+ }
6919
+ export { ARRAY_SIGNAL as A, CacheHandler as C, IdentifierArray as I, MUTATE as M, RecordArrayManager as R, Store as S, _clearCaches as _, setIdentifierGenerationMethod as a, setIdentifierUpdateMethod as b, setIdentifierForgetMethod as c, setIdentifierResetMethod as d, setKeyInfoForResource as e, isDocumentIdentifier as f, constructResource as g, coerceId as h, isStableIdentifier as i, ensureStringId as j, Collection as k, SOURCE as l, fastPush as m, notifyArray as n, removeRecordDataFor as o, peekCache as p, setRecordIdentifier as q, recordIdentifierFor as r, storeFor as s, StoreMap as t, setCacheFor as u, normalizeModelName as v, RelatedCollection as w, log as x, logGroup as y };