@ember-data/store 4.12.7 → 4.12.8
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/addon/-private.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { h as AdapterPopulatedRecordArray, C as CacheHandler, l as IDENTIFIER_ARRAY_TAG, I as IdentifierArray, M as MUTATE, I as RecordArray, R as RecordArrayManager, k as SOURCE, S as Store, _ as _clearCaches, f as coerceId, e as constructResource, g as ensureStringId, m as fastPush, i as isStableIdentifier, n as normalizeModelName, j as notifyArray, p as peekCache, r as recordIdentifierFor, o as removeRecordDataFor, c as setIdentifierForgetMethod, a as setIdentifierGenerationMethod, d as setIdentifierResetMethod, b as setIdentifierUpdateMethod, s as storeFor } from "./index-cc461d33";
|
|
@@ -164,6 +164,23 @@ async function _request2(link, options = {}) {
|
|
|
164
164
|
}));
|
|
165
165
|
return response.content;
|
|
166
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A service which an application may provide to the store via
|
|
170
|
+
* the store's `lifetimes` property to configure the behavior
|
|
171
|
+
* of the CacheHandler.
|
|
172
|
+
*
|
|
173
|
+
* The default behavior for request lifetimes is to never expire
|
|
174
|
+
* unless manually refreshed via `cacheOptions.reload` or `cacheOptions.backgroundReload`.
|
|
175
|
+
*
|
|
176
|
+
* Implementing this service allows you to programatically define
|
|
177
|
+
* when a request should be considered expired.
|
|
178
|
+
*
|
|
179
|
+
* @class <Interface> LifetimesService
|
|
180
|
+
* @public
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
const MUTATION_OPS = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
|
|
167
184
|
function isErrorDocument(document) {
|
|
168
185
|
return 'errors' in document;
|
|
169
186
|
}
|
|
@@ -171,6 +188,10 @@ function maybeUpdateUiObjects(store, request, options, document, isFromCache) {
|
|
|
171
188
|
const {
|
|
172
189
|
identifier
|
|
173
190
|
} = options;
|
|
191
|
+
if (!document) {
|
|
192
|
+
assert(`The CacheHandler expected response content but none was found`, !options.shouldHydrate);
|
|
193
|
+
return document;
|
|
194
|
+
}
|
|
174
195
|
if (isErrorDocument(document)) {
|
|
175
196
|
if (!identifier && !options.shouldHydrate) {
|
|
176
197
|
return document;
|
|
@@ -262,25 +283,54 @@ function calcShouldFetch(store, request, hasCachedValue, identifier) {
|
|
|
262
283
|
const {
|
|
263
284
|
cacheOptions
|
|
264
285
|
} = request;
|
|
265
|
-
return cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier) : false);
|
|
286
|
+
return request.op && MUTATION_OPS.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
|
|
266
287
|
}
|
|
267
288
|
function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
|
|
268
289
|
const {
|
|
269
290
|
cacheOptions
|
|
270
291
|
} = request;
|
|
271
|
-
return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier) : false));
|
|
292
|
+
return !willFetch && (cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false));
|
|
293
|
+
}
|
|
294
|
+
function isMutation(request) {
|
|
295
|
+
return Boolean(request.op && MUTATION_OPS.has(request.op));
|
|
272
296
|
}
|
|
273
297
|
function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBackgroundFetch) {
|
|
274
298
|
const {
|
|
275
299
|
store
|
|
276
300
|
} = context.request;
|
|
277
|
-
const shouldHydrate = context.request[
|
|
278
|
-
|
|
301
|
+
const shouldHydrate = context.request[EnableHydration] || false;
|
|
302
|
+
let isMut = false;
|
|
303
|
+
if (isMutation(context.request)) {
|
|
304
|
+
isMut = true;
|
|
305
|
+
// TODO should we handle multiple records in request.records by iteratively calling willCommit for each
|
|
306
|
+
const record = context.request.data?.record || context.request.records?.[0];
|
|
307
|
+
assert(`Expected to receive a list of records included in the ${context.request.op} request`, record || !shouldHydrate);
|
|
308
|
+
if (record) {
|
|
309
|
+
store.cache.willCommit(record, context);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (store.lifetimes?.willRequest) {
|
|
313
|
+
store.lifetimes.willRequest(context.request, identifier, store);
|
|
314
|
+
}
|
|
315
|
+
const promise = next(context.request).then(document => {
|
|
279
316
|
store.requestManager._pending.delete(context.id);
|
|
280
317
|
store._enableAsyncFlush = true;
|
|
281
318
|
let response;
|
|
282
319
|
store._join(() => {
|
|
283
|
-
|
|
320
|
+
if (isMutation(context.request)) {
|
|
321
|
+
const record = context.request.data?.record || context.request.records?.[0];
|
|
322
|
+
if (record) {
|
|
323
|
+
response = store.cache.didCommit(record, document);
|
|
324
|
+
|
|
325
|
+
// a mutation combined with a 204 has no cache impact when no known records were involved
|
|
326
|
+
// a createRecord with a 201 with an empty response and no known records should similarly
|
|
327
|
+
// have no cache impact
|
|
328
|
+
} else if (isCacheAffecting(document)) {
|
|
329
|
+
response = store.cache.put(document);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
response = store.cache.put(document);
|
|
333
|
+
}
|
|
284
334
|
response = maybeUpdateUiObjects(store, context.request, {
|
|
285
335
|
shouldHydrate,
|
|
286
336
|
shouldFetch,
|
|
@@ -289,6 +339,9 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
|
|
|
289
339
|
}, response, false);
|
|
290
340
|
});
|
|
291
341
|
store._enableAsyncFlush = null;
|
|
342
|
+
if (store.lifetimes?.didRequest) {
|
|
343
|
+
store.lifetimes.didRequest(context.request, document.response, identifier, store);
|
|
344
|
+
}
|
|
292
345
|
if (shouldFetch) {
|
|
293
346
|
return response;
|
|
294
347
|
} else if (shouldBackgroundFetch) {
|
|
@@ -303,15 +356,28 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
|
|
|
303
356
|
store._enableAsyncFlush = true;
|
|
304
357
|
let response;
|
|
305
358
|
store._join(() => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
359
|
+
if (isMutation(context.request)) {
|
|
360
|
+
// TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
|
|
361
|
+
// currently we let the response remain undefiend.
|
|
362
|
+
const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
|
|
363
|
+
const record = context.request.data?.record || context.request.records?.[0];
|
|
364
|
+
store.cache.commitWasRejected(record, errors);
|
|
365
|
+
// re-throw the original error to preserve `errors` property.
|
|
366
|
+
throw error;
|
|
367
|
+
} else {
|
|
368
|
+
response = store.cache.put(error);
|
|
369
|
+
response = maybeUpdateUiObjects(store, context.request, {
|
|
370
|
+
shouldHydrate,
|
|
371
|
+
shouldFetch,
|
|
372
|
+
shouldBackgroundFetch,
|
|
373
|
+
identifier
|
|
374
|
+
}, response, false);
|
|
375
|
+
}
|
|
313
376
|
});
|
|
314
377
|
store._enableAsyncFlush = null;
|
|
378
|
+
if (identifier && store.lifetimes?.didRequest) {
|
|
379
|
+
store.lifetimes.didRequest(context.request, error.response, identifier, store);
|
|
380
|
+
}
|
|
315
381
|
if (!shouldBackgroundFetch) {
|
|
316
382
|
const newError = cloneError(error);
|
|
317
383
|
newError.content = response;
|
|
@@ -320,15 +386,82 @@ function fetchContentAndHydrate(next, context, identifier, shouldFetch, shouldBa
|
|
|
320
386
|
store.notifications._flush();
|
|
321
387
|
}
|
|
322
388
|
});
|
|
389
|
+
if (!isMut) {
|
|
390
|
+
return promise;
|
|
391
|
+
}
|
|
392
|
+
assert(`Expected a mutation`, isMutation(context.request));
|
|
393
|
+
|
|
394
|
+
// for mutations we need to enqueue the promise with the requestStateService
|
|
395
|
+
// TODO should we enque a request per record in records?
|
|
396
|
+
const record = context.request.data?.record || context.request.records?.[0];
|
|
397
|
+
return store._requestCache._enqueue(promise, {
|
|
398
|
+
data: [{
|
|
399
|
+
op: 'saveRecord',
|
|
400
|
+
recordIdentifier: record,
|
|
401
|
+
options: undefined
|
|
402
|
+
}]
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function isAggregateError(error) {
|
|
406
|
+
return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
|
|
323
407
|
}
|
|
408
|
+
// TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
|
|
324
409
|
function cloneError(error) {
|
|
325
|
-
const
|
|
410
|
+
const isAggregate = isAggregateError(error);
|
|
411
|
+
const cloned = isAggregate ?
|
|
412
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
413
|
+
new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
|
|
326
414
|
cloned.stack = error.stack;
|
|
327
415
|
cloned.error = error.error;
|
|
416
|
+
|
|
417
|
+
// copy over enumerable properties
|
|
418
|
+
Object.assign(cloned, error);
|
|
328
419
|
return cloned;
|
|
329
420
|
}
|
|
330
421
|
const SkipCache = Symbol.for('ember-data:skip-cache');
|
|
331
422
|
const EnableHydration = Symbol.for('ember-data:enable-hydration');
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* A CacheHandler that adds support for using an EmberData Cache with a RequestManager.
|
|
426
|
+
*
|
|
427
|
+
* This handler will only run when a request has supplied a `store` instance. Requests
|
|
428
|
+
* issued by the store via `store.request()` will automatically have the `store` instance
|
|
429
|
+
* attached to the request.
|
|
430
|
+
*
|
|
431
|
+
* ```ts
|
|
432
|
+
* requestManager.request({
|
|
433
|
+
* store: store,
|
|
434
|
+
* url: '/api/posts',
|
|
435
|
+
* method: 'GET'
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
*
|
|
439
|
+
* When this handler elects to handle a request, it will return the raw `StructuredDocument`
|
|
440
|
+
* unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
|
|
441
|
+
* return a `Document` instance that will automatically update the UI when the cache is updated
|
|
442
|
+
* in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
|
|
443
|
+
*
|
|
444
|
+
* When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
|
|
445
|
+
* means that if desired you can issue requests that utilize the cache without needing to also
|
|
446
|
+
* utilize Record instances if desired.
|
|
447
|
+
*
|
|
448
|
+
* Said differently, you could elect to issue all requests via a RequestManager, without ever using
|
|
449
|
+
* the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
|
|
450
|
+
* necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
|
|
451
|
+
* approach of EmberData allows for this flexibility.
|
|
452
|
+
*
|
|
453
|
+
* ```ts
|
|
454
|
+
* import { EnableHydration } from '@warp-drive/core-types/request';
|
|
455
|
+
*
|
|
456
|
+
* requestManager.request({
|
|
457
|
+
* store: store,
|
|
458
|
+
* url: '/api/posts',
|
|
459
|
+
* method: 'GET',
|
|
460
|
+
* [EnableHydration]: true
|
|
461
|
+
* });
|
|
462
|
+
*
|
|
463
|
+
* @typedoc
|
|
464
|
+
*/
|
|
332
465
|
const CacheHandler = {
|
|
333
466
|
request(context, next) {
|
|
334
467
|
// if we have no cache or no cache-key skip cache handling
|
|
@@ -348,7 +481,7 @@ const CacheHandler = {
|
|
|
348
481
|
|
|
349
482
|
// if we have not skipped cache, determine if we should update behind the scenes
|
|
350
483
|
if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
|
|
351
|
-
|
|
484
|
+
const promise = fetchContentAndHydrate(next, context, identifier, false, true);
|
|
352
485
|
store.requestManager._pending.set(context.id, promise);
|
|
353
486
|
}
|
|
354
487
|
const shouldHydrate = context.request[EnableHydration] || false;
|
|
@@ -361,10 +494,11 @@ const CacheHandler = {
|
|
|
361
494
|
newError.content = content;
|
|
362
495
|
throw newError;
|
|
363
496
|
}
|
|
364
|
-
|
|
497
|
+
const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
|
|
365
498
|
shouldHydrate,
|
|
366
499
|
identifier
|
|
367
|
-
}, peeked.content, true) : peeked.content
|
|
500
|
+
}, peeked.content, true) : peeked.content;
|
|
501
|
+
return result;
|
|
368
502
|
}
|
|
369
503
|
};
|
|
370
504
|
function copyDocumentProperties(target, source) {
|
|
@@ -378,6 +512,19 @@ function copyDocumentProperties(target, source) {
|
|
|
378
512
|
target.errors = source.errors;
|
|
379
513
|
}
|
|
380
514
|
}
|
|
515
|
+
function isCacheAffecting(document) {
|
|
516
|
+
if (!isMutation(document.request)) {
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
// a mutation combined with a 204 has no cache impact when no known records were involved
|
|
520
|
+
// a createRecord with a 201 with an empty response and no known records should similarly
|
|
521
|
+
// have no cache impact
|
|
522
|
+
|
|
523
|
+
if (document.request.op === 'createRecord' && document.response?.status === 201) {
|
|
524
|
+
return document.content ? Object.keys(document.content).length > 0 : false;
|
|
525
|
+
}
|
|
526
|
+
return document.response?.status !== 204;
|
|
527
|
+
}
|
|
381
528
|
|
|
382
529
|
/*
|
|
383
530
|
* Returns the Cache instance associated with a given
|
|
@@ -7132,11 +7279,6 @@ class Store extends EmberObject {
|
|
|
7132
7279
|
[SkipCache]: true
|
|
7133
7280
|
}
|
|
7134
7281
|
};
|
|
7135
|
-
|
|
7136
|
-
// we lie here on the type because legacy doesn't have enough context
|
|
7137
|
-
cache.willCommit(identifier, {
|
|
7138
|
-
request
|
|
7139
|
-
});
|
|
7140
7282
|
return this.request(request).then(document => document.content);
|
|
7141
7283
|
}
|
|
7142
7284
|
|
|
@@ -7493,4 +7635,4 @@ function normalizeModelName(modelName) {
|
|
|
7493
7635
|
}
|
|
7494
7636
|
assert(`normalizeModelName support has been removed`);
|
|
7495
7637
|
}
|
|
7496
|
-
export { 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,
|
|
7638
|
+
export { 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, constructResource as e, coerceId as f, ensureStringId as g, Collection as h, isStableIdentifier as i, notifyArray as j, SOURCE as k, IDENTIFIER_ARRAY_TAG as l, fastPush as m, normalizeModelName as n, removeRecordDataFor as o, peekCache as p, recordIdentifierFor as r, storeFor as s };
|