@ember-data/store 4.12.0-alpha.14 → 4.12.0-alpha.16
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-35424a6b.js} +35 -10
- package/addon/index-35424a6b.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-35424a6b";
|
|
@@ -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 = new Set(['added', 'removed', 'state', 'updated']);
|
|
730
|
+
const CacheOperations$1 = new Set(['added', 'removed', 'state', 'updated']);
|
|
731
731
|
function isCacheOperationValue(value) {
|
|
732
|
-
return CacheOperations.has(value);
|
|
732
|
+
return CacheOperations$1.has(value);
|
|
733
733
|
}
|
|
734
734
|
function runLoopIsFlushing() {
|
|
735
735
|
//@ts-expect-error
|
|
@@ -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') {
|
|
@@ -6797,7 +6798,11 @@ function secretInit(record, cache, identifier, store) {
|
|
|
6797
6798
|
StoreMap.set(record, store);
|
|
6798
6799
|
setCacheFor(record, cache);
|
|
6799
6800
|
}
|
|
6801
|
+
const CacheOperations = new Set(['findRecord', 'findAll', 'query', 'queryRecord', 'findBelongsTo', 'findHasMany', 'updateRecord', 'createRecord', 'deleteRecord']);
|
|
6800
6802
|
function getHydratedContent(store, request, document) {
|
|
6803
|
+
if (!request.op || !CacheOperations.has(request.op)) {
|
|
6804
|
+
return document;
|
|
6805
|
+
}
|
|
6801
6806
|
if (Array.isArray(document.data)) {
|
|
6802
6807
|
const {
|
|
6803
6808
|
lid
|
|
@@ -6824,7 +6829,14 @@ function getHydratedContent(store, request, document) {
|
|
|
6824
6829
|
}
|
|
6825
6830
|
return managed;
|
|
6826
6831
|
} else {
|
|
6827
|
-
|
|
6832
|
+
switch (request.op) {
|
|
6833
|
+
case 'findBelongsTo':
|
|
6834
|
+
case 'queryRecord':
|
|
6835
|
+
case 'findRecord':
|
|
6836
|
+
return document.data ? store.peekRecord(document.data) : null;
|
|
6837
|
+
default:
|
|
6838
|
+
return document.data;
|
|
6839
|
+
}
|
|
6828
6840
|
}
|
|
6829
6841
|
}
|
|
6830
6842
|
function calcShouldFetch(store, request, hasCachedValue, lid) {
|
|
@@ -6833,7 +6845,7 @@ function calcShouldFetch(store, request, hasCachedValue, lid) {
|
|
|
6833
6845
|
url,
|
|
6834
6846
|
method
|
|
6835
6847
|
} = request;
|
|
6836
|
-
return cacheOptions?.reload || !hasCachedValue || store.lifetimes && lid && url && method ? store.lifetimes.isHardExpired(lid, url, method) : false;
|
|
6848
|
+
return cacheOptions?.reload || !hasCachedValue || (store.lifetimes && lid && url && method ? store.lifetimes.isHardExpired(lid, url, method) : false);
|
|
6837
6849
|
}
|
|
6838
6850
|
function calcShouldBackgroundFetch(store, request, willFetch, lid) {
|
|
6839
6851
|
const {
|
|
@@ -6841,19 +6853,32 @@ function calcShouldBackgroundFetch(store, request, willFetch, lid) {
|
|
|
6841
6853
|
url,
|
|
6842
6854
|
method
|
|
6843
6855
|
} = request;
|
|
6844
|
-
return !willFetch && (cacheOptions?.backgroundReload || store.lifetimes && lid && url && method ? store.lifetimes.isSoftExpired(lid, url, method) : false);
|
|
6856
|
+
return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && lid && url && method ? store.lifetimes.isSoftExpired(lid, url, method) : false));
|
|
6845
6857
|
}
|
|
6846
6858
|
function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetch) {
|
|
6847
6859
|
const {
|
|
6848
6860
|
store
|
|
6849
6861
|
} = context.request;
|
|
6850
6862
|
return next(context.request).then(document => {
|
|
6851
|
-
|
|
6863
|
+
store._enableAsyncFlush = true;
|
|
6864
|
+
let response;
|
|
6865
|
+
store._join(() => {
|
|
6866
|
+
response = store.cache.put(document);
|
|
6867
|
+
if (shouldFetch) {
|
|
6868
|
+
response = getHydratedContent(store, context.request, response);
|
|
6869
|
+
}
|
|
6870
|
+
});
|
|
6871
|
+
store._enableAsyncFlush = null;
|
|
6852
6872
|
if (shouldFetch) {
|
|
6853
|
-
return
|
|
6873
|
+
return response;
|
|
6854
6874
|
}
|
|
6855
6875
|
}, error => {
|
|
6856
|
-
store.
|
|
6876
|
+
store._enableAsyncFlush = true;
|
|
6877
|
+
store._join(() => {
|
|
6878
|
+
store.cache.put(error);
|
|
6879
|
+
});
|
|
6880
|
+
store._enableAsyncFlush = null;
|
|
6881
|
+
|
|
6857
6882
|
// TODO @runspired this is probably not the right thing to throw so make sure we add a test
|
|
6858
6883
|
if (!shouldBackgroundFetch) {
|
|
6859
6884
|
throw error;
|
|
@@ -6863,7 +6888,7 @@ function fetchContentAndHydrate(next, context, shouldFetch, shouldBackgroundFetc
|
|
|
6863
6888
|
const CacheHandler = {
|
|
6864
6889
|
request(context, next) {
|
|
6865
6890
|
// 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) {
|
|
6891
|
+
if (!context.request.store || context.request.op && CacheOperations.has(context.request.op) && !context.request.url) {
|
|
6867
6892
|
return next(context.request);
|
|
6868
6893
|
}
|
|
6869
6894
|
const {
|