@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 +47 -40
- package/addon/-private.js +1 -1
- package/addon/{index-123f3e7e.js → index-f7d3d5e5.js} +31 -11
- package/addon/index-f7d3d5e5.js.map +1 -0
- package/addon/index.js +1 -1
- package/addon-main.js +2 -2
- package/package.json +8 -8
- package/addon/index-123f3e7e.js.map +0 -1
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
|
|
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
|
|
85
|
+
> **Note** [2] The `ember-data` package automatically includes the `@ember-data/json-api` cache for you.
|
|
86
86
|
|
|
87
|
-
###
|
|
87
|
+
### Handling Requests
|
|
88
88
|
|
|
89
|
-
When *Ember***Data** needs to fetch or save data it will pass that request to your application's `
|
|
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
|
|
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
|
-
|
|
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
|
-
```
|
|
95
|
+
```ts
|
|
96
96
|
import Store from '@ember-data/store';
|
|
97
|
-
import
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
109
|
+
**Using RequestManager as a Service**
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Alternatively if you have configured the `RequestManager` to be a service you may re-use it.
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
import
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
|
6867
|
+
return response;
|
|
6854
6868
|
}
|
|
6855
6869
|
}, error => {
|
|
6856
|
-
store.
|
|
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
|
|
6866
|
-
if (!context.request.store || context.request.
|
|
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
|
-
|
|
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) {
|