Skip to content

Commit f3d2b74

Browse files
feat: improved lifetimes-service capabilities (#9224)
* feat: improved lifetimes-service capabilities (#9163) * feat: improved lifetimes-service capabilities * fix lint * fix lint * doc: Fix paths in transform deprecations (#8892) This fixes the paths in the deprecation messages about "auto-magically installing" transforms. --------- Co-authored-by: Eric Kelly <602204+HeroicEric@users.noreply.github.com>
1 parent a04e060 commit f3d2b74

File tree

8 files changed

+137
-23
lines changed

8 files changed

+137
-23
lines changed

packages/-ember-data/app/transforms/boolean.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { deprecate } from '@ember/debug';
33
export { BooleanTransform as default } from '@ember-data/serializer/-private';
44

55
deprecate(
6-
"You are relying on ember-data auto-magically installing the BooleanTransform. Use `export { BooleanTransform as default } from 'ember-data/serializer/transform';` in app/transforms/boolean.js instead",
6+
"You are relying on ember-data auto-magically installing the BooleanTransform. Use `export { BooleanTransform as default } from '@ember-data/serializer/transform';` in app/transforms/boolean.js instead",
77
false,
88
{
99
id: 'ember-data:deprecate-legacy-imports',

packages/-ember-data/app/transforms/date.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { deprecate } from '@ember/debug';
33
export { DateTransform as default } from '@ember-data/serializer/-private';
44

55
deprecate(
6-
"You are relying on ember-data auto-magically installing the DateTransform. Use `export { DateTransform as default } from 'ember-data/serializer/transform';` in app/transforms/date.js instead",
6+
"You are relying on ember-data auto-magically installing the DateTransform. Use `export { DateTransform as default } from '@ember-data/serializer/transform';` in app/transforms/date.js instead",
77
false,
88
{
99
id: 'ember-data:deprecate-legacy-imports',

packages/-ember-data/app/transforms/number.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { deprecate } from '@ember/debug';
33
export { NumberTransform as default } from '@ember-data/serializer/-private';
44

55
deprecate(
6-
"You are relying on ember-data auto-magically installing the NumberTransform. Use `export { NumberTransform as default } from 'ember-data/serializer/transform';` in app/transforms/number.js instead",
6+
"You are relying on ember-data auto-magically installing the NumberTransform. Use `export { NumberTransform as default } from '@ember-data/serializer/transform';` in app/transforms/number.js instead",
77
false,
88
{
99
id: 'ember-data:deprecate-legacy-imports',

packages/-ember-data/app/transforms/string.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { deprecate } from '@ember/debug';
33
export { StringTransform as default } from '@ember-data/serializer/-private';
44

55
deprecate(
6-
"You are relying on ember-data auto-magically installing the StringTransform. Use `export { StringTransform as default } from 'ember-data/serializer/transform';` in app/transforms/string.js instead",
6+
"You are relying on ember-data auto-magically installing the StringTransform. Use `export { StringTransform as default } from '@ember-data/serializer/transform';` in app/transforms/string.js instead",
77
false,
88
{
99
id: 'ember-data:deprecate-legacy-imports',

packages/request-utils/src/index.ts

+34-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { assert } from '@ember/debug';
1+
import { assert, deprecate } from '@ember/debug';
22

3-
import type Store from '@ember-data/store';
4-
import { StableDocumentIdentifier } from '@ember-data/types/cache/identifier';
3+
import type { StableDocumentIdentifier } from '@ember-data/types/cache/identifier';
4+
import { Cache } from '@ember-data/types/q/cache';
55

66
/**
77
* Simple utility function to assist in url building,
@@ -602,7 +602,7 @@ export type LifetimesConfig = { apiCacheSoftExpires: number; apiCacheHardExpires
602602
* export class Store extends DataStore {
603603
* constructor(args) {
604604
* super(args);
605-
* this.lifetimes = new LifetimesService(this, { apiCacheSoftExpires: 30_000, apiCacheHardExpires: 60_000 });
605+
* this.lifetimes = new LifetimesService({ apiCacheSoftExpires: 30_000, apiCacheHardExpires: 60_000 });
606606
* }
607607
* }
608608
* ```
@@ -611,22 +611,42 @@ export type LifetimesConfig = { apiCacheSoftExpires: number; apiCacheHardExpires
611611
* @public
612612
* @module @ember-data/request-utils
613613
*/
614-
// TODO this doesn't get documented correctly on the website because it shares a class name
615-
// with the interface expected by the Store service
616614
export class LifetimesService {
617-
declare store: Store;
618615
declare config: LifetimesConfig;
619-
constructor(store: Store, config: LifetimesConfig) {
620-
this.store = store;
621-
this.config = config;
616+
617+
constructor(config: LifetimesConfig) {
618+
const _config = arguments.length === 1 ? config : (arguments[1] as unknown as LifetimesConfig);
619+
deprecate(
620+
`Passing a Store to the LifetimesService is deprecated, please pass only a config instead.`,
621+
arguments.length === 1,
622+
{
623+
id: 'ember-data:request-utils:lifetimes-service-store-arg',
624+
since: {
625+
enabled: '5.4',
626+
available: '5.4',
627+
},
628+
for: '@ember-data/request-utils',
629+
until: '6.0',
630+
}
631+
);
632+
assert(`You must pass a config to the LifetimesService`, _config);
633+
assert(
634+
`You must pass a apiCacheSoftExpires to the LifetimesService`,
635+
typeof _config.apiCacheSoftExpires === 'number'
636+
);
637+
assert(
638+
`You must pass a apiCacheHardExpires to the LifetimesService`,
639+
typeof _config.apiCacheHardExpires === 'number'
640+
);
641+
this.config = _config;
622642
}
623643

624-
isHardExpired(identifier: StableDocumentIdentifier): boolean {
625-
const cached = this.store.cache.peekRequest(identifier);
644+
isHardExpired(identifier: StableDocumentIdentifier, cache: Cache): boolean {
645+
const cached = cache.peekRequest(identifier);
626646
return !cached || !cached.response || isStale(cached.response.headers, this.config.apiCacheHardExpires);
627647
}
628-
isSoftExpired(identifier: StableDocumentIdentifier): boolean {
629-
const cached = this.store.cache.peekRequest(identifier);
648+
isSoftExpired(identifier: StableDocumentIdentifier, cache: Cache): boolean {
649+
const cached = cache.peekRequest(identifier);
630650
return !cached || !cached.response || isStale(cached.response.headers, this.config.apiCacheSoftExpires);
631651
}
632652
}

packages/store/src/-private/cache-handler.ts

+94-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* @module @ember-data/store
3+
*/
14
import { assert } from '@ember/debug';
25

36
import type {
@@ -15,16 +18,91 @@ import type {
1518
ResourceErrorDocument,
1619
} from '@ember-data/types/cache/document';
1720
import type { StableDocumentIdentifier } from '@ember-data/types/cache/identifier';
21+
import { Cache } from '@ember-data/types/q/cache';
1822
import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
1923
import type { JsonApiError } from '@ember-data/types/q/record-data-json-api';
2024
import type { RecordInstance } from '@ember-data/types/q/record-instance';
2125
import type { CreateRequestOptions, DeleteRequestOptions, UpdateRequestOptions } from '@ember-data/types/request';
2226

2327
import { Document } from './document';
2428

29+
/**
30+
* A service which an application may provide to the store via
31+
* the store's `lifetimes` property to configure the behavior
32+
* of the CacheHandler.
33+
*
34+
* The default behavior for request lifetimes is to never expire
35+
* unless manually refreshed via `cacheOptions.reload` or `cacheOptions.backgroundReload`.
36+
*
37+
* Implementing this service allows you to programatically define
38+
* when a request should be considered expired.
39+
*
40+
* @class <Interface> LifetimesService
41+
* @public
42+
*/
2543
export interface LifetimesService {
26-
isHardExpired(identifier: StableDocumentIdentifier): boolean;
27-
isSoftExpired(identifier: StableDocumentIdentifier): boolean;
44+
/**
45+
* Invoked to determine if the request may be fulfilled from cache
46+
* if possible.
47+
*
48+
* Note, this is only invoked if the request has a cache-key.
49+
*
50+
* If no cache entry is found or the entry is hard expired,
51+
* the request will be fulfilled from the configured request handlers
52+
* and the cache will be updated before returning the response.
53+
*
54+
* @method isHardExpired
55+
* @public
56+
* @param {StableDocumentIdentifier} identifier
57+
* @param {Cache} cache
58+
* @returns {boolean} true if the request is considered hard expired
59+
*/
60+
isHardExpired(identifier: StableDocumentIdentifier, cache: Cache): boolean;
61+
/**
62+
* Invoked if `isHardExpired` is false to determine if the request
63+
* should be update behind the scenes if cache data is already available.
64+
*
65+
* Note, this is only invoked if the request has a cache-key.
66+
*
67+
* If true, the request will be fulfilled from cache while a backgrounded
68+
* request is made to update the cache via the configured request handlers.
69+
*
70+
* @method isSoftExpired
71+
* @public
72+
* @param {StableDocumentIdentifier} identifier
73+
* @param {Cache} cache
74+
* @returns {boolean} true if the request is considered soft expired
75+
*/
76+
isSoftExpired(identifier: StableDocumentIdentifier, cache: Cache): boolean;
77+
78+
/**
79+
* Invoked when a request will be sent to the configured request handlers.
80+
* This is invoked for both foreground and background requests.
81+
*
82+
* Note, this is only invoked if the request has a cache-key.
83+
*
84+
* @method willRequest [Optional]
85+
* @public
86+
* @param {StableDocumentIdentifier} identifier
87+
* @param {Cache} cache
88+
* @returns {void}
89+
*/
90+
willRequest?(identifier: StableDocumentIdentifier, cache: Cache): void;
91+
92+
/**
93+
* Invoked when a request has been fulfilled from the configured request handlers.
94+
* This is invoked for both foreground and background requests once the cache has
95+
* been updated.
96+
*
97+
* Note, this is only invoked if the request has a cache-key.
98+
*
99+
* @method didRequest [Optional]
100+
* @public
101+
* @param {StableDocumentIdentifier} identifier
102+
* @param {Cache} cache
103+
* @returns {void}
104+
*/
105+
didRequest?(identifier: StableDocumentIdentifier, cache: Cache): void;
28106
}
29107

30108
export type StoreRequestInfo = ImmutableRequestInfo;
@@ -168,7 +246,7 @@ function calcShouldFetch(
168246
(request.op && MUTATION_OPS.has(request.op)) ||
169247
cacheOptions?.reload ||
170248
!hasCachedValue ||
171-
(store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier) : false)
249+
(store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store.cache) : false)
172250
);
173251
}
174252

@@ -182,7 +260,7 @@ function calcShouldBackgroundFetch(
182260
return (
183261
!willFetch &&
184262
(cacheOptions?.backgroundReload ||
185-
(store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier) : false))
263+
(store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store.cache) : false))
186264
);
187265
}
188266

@@ -211,6 +289,10 @@ function fetchContentAndHydrate<T>(
211289
store.cache.willCommit(record, context);
212290
}
213291

292+
if (identifier && store.lifetimes?.willRequest) {
293+
store.lifetimes.willRequest(identifier, store.cache);
294+
}
295+
214296
const promise = next(context.request).then(
215297
(document) => {
216298
store.requestManager._pending.delete(context.id);
@@ -232,6 +314,10 @@ function fetchContentAndHydrate<T>(
232314
});
233315
store._enableAsyncFlush = null;
234316

317+
if (identifier && store.lifetimes?.didRequest) {
318+
store.lifetimes.didRequest(identifier, store.cache);
319+
}
320+
235321
if (shouldFetch) {
236322
return response!;
237323
} else if (shouldBackgroundFetch) {
@@ -274,6 +360,10 @@ function fetchContentAndHydrate<T>(
274360
});
275361
store._enableAsyncFlush = null;
276362

363+
if (identifier && store.lifetimes?.didRequest) {
364+
store.lifetimes.didRequest(identifier, store.cache);
365+
}
366+
277367
if (!shouldBackgroundFetch) {
278368
const newError = cloneError(error);
279369
newError.content = response;

tests/docs/fixtures/expected.js

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ module.exports = {
3333
'(public) @ember-data/store IdentifierCache#getOrCreateDocumentIdentifier',
3434
'(public) @ember-data/store Store#registerSchema',
3535
'(public) @ember-data/store Store#schema',
36+
'(public) @ember-data/store <Interface> LifetimesService#didRequest [Optional]',
37+
'(public) @ember-data/store <Interface> LifetimesService#isHardExpired',
38+
'(public) @ember-data/store <Interface> LifetimesService#isSoftExpired',
39+
'(public) @ember-data/store <Interface> LifetimesService#willRequest [Optional]',
3640
'(private) @ember-data/adapter BuildURLMixin#_buildURL',
3741
'(private) @ember-data/adapter BuildURLMixin#urlPrefix',
3842
'(private) @ember-data/adapter/json-api JSONAPIAdapter#ajaxOptions',

tests/recommended-json-api/app/services/store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class Store extends DataStore {
2121
manager.useCache(CacheHandler);
2222

2323
this.registerSchema(buildSchema(this));
24-
this.lifetimes = new LifetimesService(this, CONFIG);
24+
this.lifetimes = new LifetimesService(CONFIG);
2525
}
2626

2727
createCache(capabilities: CacheCapabilitiesManager): Cache {

0 commit comments

Comments
 (0)