@ember-data/store 4.12.0-beta.6 → 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-35424a6b.js → index-f7d3d5e5.js} +14 -19
- package/addon/index-f7d3d5e5.js.map +1 -0
- package/addon/index.js +1 -1
- package/addon-main.js +1 -1
- package/package.json +8 -8
- package/addon/index-35424a6b.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";
|
|
@@ -727,9 +727,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
|
|
|
727
727
|
return desc;
|
|
728
728
|
}
|
|
729
729
|
let tokenId = 0;
|
|
730
|
-
const CacheOperations
|
|
730
|
+
const CacheOperations = new Set(['added', 'removed', 'state', 'updated']);
|
|
731
731
|
function isCacheOperationValue(value) {
|
|
732
|
-
return CacheOperations
|
|
732
|
+
return CacheOperations.has(value);
|
|
733
733
|
}
|
|
734
734
|
function runLoopIsFlushing() {
|
|
735
735
|
//@ts-expect-error
|
|
@@ -4888,8 +4888,10 @@ class Store {
|
|
|
4888
4888
|
// we lazily set the cache handler when we issue the first request
|
|
4889
4889
|
// because constructor doesn't allow for this to run after
|
|
4890
4890
|
// the user has had the chance to set the prop.
|
|
4891
|
+
const storeSymbol = Symbol.for('ember-data:enable-hydration');
|
|
4891
4892
|
let opts = {
|
|
4892
|
-
store: this
|
|
4893
|
+
store: this,
|
|
4894
|
+
[storeSymbol]: true
|
|
4893
4895
|
};
|
|
4894
4896
|
if (macroCondition(getOwnConfig().env.TESTING)) {
|
|
4895
4897
|
if (this.DISABLE_WAITER) {
|
|
@@ -6798,11 +6800,7 @@ function secretInit(record, cache, identifier, store) {
|
|
|
6798
6800
|
StoreMap.set(record, store);
|
|
6799
6801
|
setCacheFor(record, cache);
|
|
6800
6802
|
}
|
|
6801
|
-
const CacheOperations = new Set(['findRecord', 'findAll', 'query', 'queryRecord', 'findBelongsTo', 'findHasMany', 'updateRecord', 'createRecord', 'deleteRecord']);
|
|
6802
6803
|
function getHydratedContent(store, request, document) {
|
|
6803
|
-
if (!request.op || !CacheOperations.has(request.op)) {
|
|
6804
|
-
return document;
|
|
6805
|
-
}
|
|
6806
6804
|
if (Array.isArray(document.data)) {
|
|
6807
6805
|
const {
|
|
6808
6806
|
lid
|
|
@@ -6829,14 +6827,9 @@ function getHydratedContent(store, request, document) {
|
|
|
6829
6827
|
}
|
|
6830
6828
|
return managed;
|
|
6831
6829
|
} else {
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
case 'findRecord':
|
|
6836
|
-
return document.data ? store.peekRecord(document.data) : null;
|
|
6837
|
-
default:
|
|
6838
|
-
return document.data;
|
|
6839
|
-
}
|
|
6830
|
+
return Object.assign({}, document, {
|
|
6831
|
+
data: document.data ? store.peekRecord(document.data) : null
|
|
6832
|
+
});
|
|
6840
6833
|
}
|
|
6841
6834
|
}
|
|
6842
6835
|
function calcShouldFetch(store, request, hasCachedValue, lid) {
|
|
@@ -6859,12 +6852,13 @@ function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetc
|
|
|
6859
6852
|
const {
|
|
6860
6853
|
store
|
|
6861
6854
|
} = context.request;
|
|
6855
|
+
const shouldHydrate = context.request[Symbol.for('ember-data:enable-hydration')] || false;
|
|
6862
6856
|
return next(context.request).then(document => {
|
|
6863
6857
|
store._enableAsyncFlush = true;
|
|
6864
6858
|
let response;
|
|
6865
6859
|
store._join(() => {
|
|
6866
6860
|
response = store.cache.put(document);
|
|
6867
|
-
if (shouldFetch) {
|
|
6861
|
+
if (shouldFetch && shouldHydrate) {
|
|
6868
6862
|
response = getHydratedContent(store, context.request, response);
|
|
6869
6863
|
}
|
|
6870
6864
|
});
|
|
@@ -6887,8 +6881,8 @@ function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetc
|
|
|
6887
6881
|
}
|
|
6888
6882
|
const CacheHandler = {
|
|
6889
6883
|
request(context, next) {
|
|
6890
|
-
// if we
|
|
6891
|
-
if (!context.request.store ||
|
|
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)) {
|
|
6892
6886
|
return next(context.request);
|
|
6893
6887
|
}
|
|
6894
6888
|
const {
|
|
@@ -6916,7 +6910,8 @@ const CacheHandler = {
|
|
|
6916
6910
|
if ('error' in peeked) {
|
|
6917
6911
|
throw peeked.error;
|
|
6918
6912
|
}
|
|
6919
|
-
|
|
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);
|
|
6920
6915
|
}
|
|
6921
6916
|
};
|
|
6922
6917
|
function normalizeModelName(modelName) {
|