@ember-data/store 4.12.0-beta.5 → 4.12.0-beta.7

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.
package/README.md CHANGED
@@ -80,66 +80,64 @@ class extends Store {
80
80
 
81
81
  Now that we have a `cache` let's setup something to handle fetching and saving data via our API.
82
82
 
83
- > Note: [1] the cache from `@ember-data/json-api` is a special cache: if the package is present the `createCache` hook will automatically do the above wiring if the hook is not implemented. We still recommend implementing the hook.
83
+ > **Note** [1] the cache from `@ember-data/json-api` is a special cache: if the package is present the `createCache` hook will automatically do the above wiring if the hook is not implemented. We still recommend implementing the hook.
84
84
  >
85
- > Note: [2] The `ember-data` package automatically includes the `@ember-data/json-api` cache for you.
85
+ > **Note** [2] The `ember-data` package automatically includes the `@ember-data/json-api` cache for you.
86
86
 
87
- ### Adding An Adapter
87
+ ### Handling Requests
88
88
 
89
- When *Ember***Data** needs to fetch or save data it will pass that request to your application's `Adapter` for fulfillment. How this fulfillment occurs (in-memory, device storage, via single or multiple API requests, etc.) is up to that Adapter.
89
+ When *Ember***Data** needs to fetch or save data it will pass that request to your application's `RequestManager` for fulfillment. How this fulfillment occurs (in-memory, device storage, via single or multiple API requests, etc.) is then up to the registered request handlers.
90
90
 
91
- To start, let's install a `JSON:API` adapter. If your app uses `GraphQL` or `REST` other adapters may better fit your data. You can author your own adapter by creating one that conforms to the [spec]().
91
+ To start, let's install the `FetchManager` from `@ember-data/request` and the basic `Fetch` handler from ``@ember-data/request/fetch`.
92
92
 
93
- The package `@ember-data/adapter` provides a `JSON:API` adapter we can use. After installing it, we can configure the store to use this adapter.
93
+ > **Note** If your app uses `GraphQL`, `REST` or different conventions for `JSON:API` than your cache expects, other handlers may better fit your data. You can author your own handler by creating one that conforms to the [handler interface](https://github.com/emberjs/data/tree/main/packages/request#handling-requests).
94
94
 
95
- ```js
95
+ ```ts
96
96
  import Store from '@ember-data/store';
97
- import Adapter from '@ember-data/adapter/json-api';
98
-
99
- class extends Store {
100
- #adapter = new Adapter();
101
-
102
- adapterFor() {
103
- return this.#adapter;
97
+ import RequestManager from '@ember-data/request';
98
+ import Fetch from '@ember-data/request/fetch';
99
+
100
+ export default class extends Store {
101
+ constructor() {
102
+ super(...arguments);
103
+ this.requestManager = new RequestManager();
104
+ this.requestManager.use([Fetch]);
104
105
  }
105
106
  }
106
107
  ```
107
108
 
108
- If you want to know more about using Adapters with Ember read the next section, else lets skip to [Presenting Data from the Cache](#presenting-data-from-the-cache) to configure how our application will interact with our data.
109
+ **Using RequestManager as a Service**
109
110
 
110
- #### Using with Ember
111
+ Alternatively if you have configured the `RequestManager` to be a service you may re-use it.
111
112
 
112
- Note: If you are using Ember and would like to make use of `service` injections in your adapter, you will want to additionally `setOwner` for the Adapter.
113
-
114
- ```js
115
- import Store from '@ember-data/store';
116
- import Adapter from '@ember-data/adapter/json-api';
117
- import { getOwner, setOwner } from '@ember/application';
113
+ *app/services/request.js*
114
+ ```ts
115
+ import RequestManager from '@ember-data/request';
116
+ import Fetch from '@ember-data/request/fetch';
118
117
 
119
- class extends Store {
120
- #adapter = null;
121
-
122
- adapterFor() {
123
- let adapter = this.#adapter;
124
- if (!adapter) {
125
- const owner = getOwner(this);
126
- adapter = new Adapter();
127
- setOwner(adapter, owner);
128
- this.#adapter = adapter;
129
- }
130
-
131
- return adapter;
118
+ export default class extends RequestManager {
119
+ constructor(createArgs) {
120
+ super(createArgs);
121
+ this.use([Fetch]);
132
122
  }
133
123
  }
134
124
  ```
135
125
 
136
- By default when using with Ember you only need to implement this hook if you want your adapter usage to be statically analyzeable. *Ember***Data** will attempt to resolve adapters using Ember's resolver. To provide a single Adapter for your application like the above you would provide it as the default export of the file `app/adapters/application.{js/ts}`
126
+ *app/services/store.js*
127
+ ```ts
128
+ import Store from '@ember-data/store';
129
+ import { service } from '@ember/service';
130
+
131
+ export default class extends Store {
132
+ @service('request') requestManager
133
+ }
134
+ ```
137
135
 
138
136
  ### Presenting Data from the Cache
139
137
 
140
138
  Now that we have a source and a cach for our data, we need to configure how the Store delivers that data back to our application. We do this via the hook `instantiateRecord`, which allows us to transform the data for a resource before handing it to the application.
141
139
 
142
- A naive way to present the data would be to return it as JSON. Typically instead this hook will be used to add reactivity and make each uniue resource a singleton, ensuring that if the cache updates our presented data will reflect the new state.
140
+ A naive way to present the data would be to return it as JSON. Typically instead this hook will be used to add reactivity and make each unique resource a singleton, ensuring that if the cache updates our presented data will reflect the new state.
143
141
 
144
142
  Below is an example of using the hooks `instantiateRecord` and a `teardownRecord` to provide minimal read-only reactive state for simple resources.
145
143
 
@@ -156,25 +154,34 @@ class extends Store {
156
154
  record.type = identifier.type;
157
155
  record.id = identifier.id;
158
156
 
159
- notifications.subscribe(identifier, (_, change) => {
157
+ // update the TrackedObject whenever attributes change
158
+ const token = notifications.subscribe(identifier, (_, change) => {
160
159
  if (change === 'attributes') {
161
160
  Object.assign(record, cache.peek(identifier));
162
161
  }
163
162
  });
164
163
 
164
+ record.destroy = () => {
165
+ this.notifications.unsubscribe(token);
166
+ };
167
+
165
168
  return record;
166
169
  }
170
+
171
+ teardownRecord(record: FakeRecord) {
172
+ record.destroy();
173
+ }
167
174
  }
168
175
  ```
169
176
 
170
177
  Because `instantiateRecord` is opaque to the nature of the record, an implementation can be anything from a fairly simple object to a robust proxy that intelligently links together associated records through relationships.
171
178
 
172
- This also enables creating a record that separates `edit` flows from `create` flows entirely. A record class might choose to implement a `checkout`method that gives access to an editable instance while the primary record continues to be read-only and reflect only persisted (non-mutated) state.
179
+ This also enables creating a record that separates `edit` flows from `create` flows entirely. A record class might choose to implement a `checkout` method that gives access to an editable instance while the primary record continues to be read-only and reflect only persisted (non-mutated) state.
173
180
 
174
181
  Typically you will choose an existing record implementation such as `@ember-data/model` for your application.
175
182
 
176
183
  Because of the boundaries around instantiation and the cache, record implementations should be capable of interop both with each other and with any `Cache`. Due to this, if needed an application can utilize multiple record implementations and multiple cache implementations either to support enhanced features for only a subset of records or to be able to incrementally migrate from one record/cache to another record or cache.
177
184
 
178
- > Note: [1] `@ember-data/model` is a special record implementation: if the package is present the `instantiateRecord` hook will automatically do the above wiring if the hook is not implemented. Due to the complexity of this legacy package's use of Ember's resolver, we do not recommend wiring this package manually.
185
+ > Note: [1] `@ember-data/model` is a special record implementation: currently, if the package is present the `instantiateRecord` hook will automatically do the above wiring if the hook is not implemented.
179
186
  >
180
187
  > Note: [2] The `ember-data` package automatically includes the `@ember-data/model` implementation for you.
package/addon/-private.js CHANGED
@@ -1 +1 @@
1
- export { f as AdapterPopulatedRecordArray, C as CacheHandler, j as IDENTIFIER_ARRAY_TAG, I as IdentifierArray, M as MUTATE, I as RecordArray, R as RecordArrayManager, h as SOURCE, S as Store, _ as _clearCaches, e as coerceId, k as fastPush, i as isStableIdentifier, n as normalizeModelName, g as notifyArray, p as peekCache, r as recordIdentifierFor, l as removeRecordDataFor, c as setIdentifierForgetMethod, a as setIdentifierGenerationMethod, d as setIdentifierResetMethod, b as setIdentifierUpdateMethod, s as storeFor } from "./index-123f3e7e";
1
+ export { f as AdapterPopulatedRecordArray, C as CacheHandler, j as IDENTIFIER_ARRAY_TAG, I as IdentifierArray, M as MUTATE, I as RecordArray, R as RecordArrayManager, h as SOURCE, S as Store, _ as _clearCaches, e as coerceId, k as fastPush, i as isStableIdentifier, n as normalizeModelName, g as notifyArray, p as peekCache, r as recordIdentifierFor, l as removeRecordDataFor, c as setIdentifierForgetMethod, a as setIdentifierGenerationMethod, d as setIdentifierResetMethod, b as setIdentifierUpdateMethod, s as storeFor } from "./index-f7d3d5e5";
@@ -2826,7 +2826,7 @@ class InstanceCache {
2826
2826
  return reference;
2827
2827
  }
2828
2828
  recordIsLoaded(identifier, filterDeleted = false) {
2829
- const cache = macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA) ? this.__instances.resourceCache.get(identifier) : this.cache;
2829
+ const cache = macroCondition(getOwnConfig().deprecations.DEPRECATE_V1_RECORD_DATA) ? this.__instances.resourceCache.get(identifier) || this.cache : this.cache;
2830
2830
  if (!cache) {
2831
2831
  return false;
2832
2832
  }
@@ -4294,6 +4294,7 @@ class RecordArrayManager {
4294
4294
  this._managed = new Set();
4295
4295
  this._pending = new Map();
4296
4296
  this._staged = new Map();
4297
+ this._keyedArrays = new Map();
4297
4298
  this._identifiers = RecordArraysCache;
4298
4299
  this._subscription = this.store.notifications.subscribe('resource', (identifier, type) => {
4299
4300
  if (type === 'added') {
@@ -4887,8 +4888,10 @@ class Store {
4887
4888
  // we lazily set the cache handler when we issue the first request
4888
4889
  // because constructor doesn't allow for this to run after
4889
4890
  // the user has had the chance to set the prop.
4891
+ const storeSymbol = Symbol.for('ember-data:enable-hydration');
4890
4892
  let opts = {
4891
- store: this
4893
+ store: this,
4894
+ [storeSymbol]: true
4892
4895
  };
4893
4896
  if (macroCondition(getOwnConfig().env.TESTING)) {
4894
4897
  if (this.DISABLE_WAITER) {
@@ -6824,7 +6827,9 @@ function getHydratedContent(store, request, document) {
6824
6827
  }
6825
6828
  return managed;
6826
6829
  } else {
6827
- return document.data ? store.peekRecord(document.data) : null;
6830
+ return Object.assign({}, document, {
6831
+ data: document.data ? store.peekRecord(document.data) : null
6832
+ });
6828
6833
  }
6829
6834
  }
6830
6835
  function calcShouldFetch(store, request, hasCachedValue, lid) {
@@ -6833,7 +6838,7 @@ function calcShouldFetch(store, request, hasCachedValue, lid) {
6833
6838
  url,
6834
6839
  method
6835
6840
  } = request;
6836
- return cacheOptions?.reload || !hasCachedValue || store.lifetimes && lid && url && method ? store.lifetimes.isHardExpired(lid, url, method) : false;
6841
+ return cacheOptions?.reload || !hasCachedValue || (store.lifetimes && lid && url && method ? store.lifetimes.isHardExpired(lid, url, method) : false);
6837
6842
  }
6838
6843
  function calcShouldBackgroundFetch(store, request, willFetch, lid) {
6839
6844
  const {
@@ -6841,19 +6846,33 @@ function calcShouldBackgroundFetch(store, request, willFetch, lid) {
6841
6846
  url,
6842
6847
  method
6843
6848
  } = request;
6844
- return !willFetch && (cacheOptions?.backgroundReload || store.lifetimes && lid && url && method ? store.lifetimes.isSoftExpired(lid, url, method) : false);
6849
+ return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && lid && url && method ? store.lifetimes.isSoftExpired(lid, url, method) : false));
6845
6850
  }
6846
6851
  function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetch) {
6847
6852
  const {
6848
6853
  store
6849
6854
  } = context.request;
6855
+ const shouldHydrate = context.request[Symbol.for('ember-data:enable-hydration')] || false;
6850
6856
  return next(context.request).then(document => {
6851
- const response = store.cache.put(document);
6857
+ store._enableAsyncFlush = true;
6858
+ let response;
6859
+ store._join(() => {
6860
+ response = store.cache.put(document);
6861
+ if (shouldFetch && shouldHydrate) {
6862
+ response = getHydratedContent(store, context.request, response);
6863
+ }
6864
+ });
6865
+ store._enableAsyncFlush = null;
6852
6866
  if (shouldFetch) {
6853
- return getHydratedContent(store, context.request, response);
6867
+ return response;
6854
6868
  }
6855
6869
  }, error => {
6856
- store.cache.put(error);
6870
+ store._enableAsyncFlush = true;
6871
+ store._join(() => {
6872
+ store.cache.put(error);
6873
+ });
6874
+ store._enableAsyncFlush = null;
6875
+
6857
6876
  // TODO @runspired this is probably not the right thing to throw so make sure we add a test
6858
6877
  if (!shouldBackgroundFetch) {
6859
6878
  throw error;
@@ -6862,8 +6881,8 @@ function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetc
6862
6881
  }
6863
6882
  const CacheHandler = {
6864
6883
  request(context, next) {
6865
- // if we are a legacy request or did not originate from the store, skip cache handling
6866
- if (!context.request.store || context.request.op && !context.request.url) {
6884
+ // if we have no cache or no cache-key skip cache handling
6885
+ if (!context.request.store || !(context.request.cacheOptions?.key || context.request.url)) {
6867
6886
  return next(context.request);
6868
6887
  }
6869
6888
  const {
@@ -6891,7 +6910,8 @@ const CacheHandler = {
6891
6910
  if ('error' in peeked) {
6892
6911
  throw peeked.error;
6893
6912
  }
6894
- return Promise.resolve(getHydratedContent(store, context.request, peeked.content));
6913
+ const shouldHydrate = context.request[Symbol.for('ember-data:enable-hydration')] || false;
6914
+ return Promise.resolve(shouldHydrate ? getHydratedContent(store, context.request, peeked.content) : peeked.content);
6895
6915
  }
6896
6916
  };
6897
6917
  function normalizeModelName(modelName) {