@apollo/datasource-rest
Advanced tools
Changelog
5.1.1
fcd05fa
Thanks @AaronMoat! - Don't crash when receiving non-string, non-array headersChangelog
5.1.0
#186 5ac9b52
Thanks @js-lowes! - Customize the logger used by RESTDataSource
.
By default the RESTDataSource
will use console
.
Common use cases would be to override the default logger with pino
or winston
.
E.g.
const pino = require('pino');
const loggerPino = pino({});
const dataSource = new (class extends RESTDataSource {})({
logger: loggerPino,
});
In the example above, all logging calls made by the RESTDataSource
will now use the pino
logger instead of the console
logger.
Changelog
5.0.2
#159 ee018a7
Thanks @trevor-scheer! - Update http-cache-semantics
package to latest patch, resolving a security
issue.
Unlike many security updates Apollo repos receive, this is an actual (non-dev) dependency of this package which means it is actually a user-facing security issue.
The potential impact of this issue is limited to a DOS attack (via an inefficient regex).
This security issue would only affect you if either:
cache-control
request headerscache-control
headersSince http-cache-semantics
is a careted (^) dependency in this package, the
security issue can (and might already) be resolved via a package-lock.json
update within your project (possibly triggered by npm audit
or another
dependency update which has already updated its version of the package in
question). If npm ls http-cache-semantics
reveals a tree of dependencies which
only include the 4.1.1
version (and no references to any previous versions)
then you are currently unaffected and this patch should have (for all intents
and purpose) no effect.
More details available here: https://github.com/advisories/GHSA-rc47-6667-2j5j
#160 786c44f
Thanks @trevor-scheer! - Add missing @apollo/utils.withrequired
type dependency which is part of the
public typings (via the AugmentedRequest
type).
#154 bb0cff0
Thanks @JustinSomers! - Addresses duplicate content-type header bug due to upper-cased headers being forwarded. This change instead maps all headers to lowercased headers.
Changelog
5.0.1
c9ffa7f
Thanks @trevor-scheer! - Create intermediate request types (PostRequest
, etc.) for consistency and export them.
Export DataSourceRequest
, DataSourceConfig
, and DataSourceFetchResult
types.Changelog
5.0.0
Version 5 of RESTDataSource
addresses many of the long-standing issues and PRs that have existed in this repository (and its former location in the apollo-server
repository). While this version does include a number of breaking changes, our hope is that the updated API makes this package more usable and its caching-related behavior less surprising.
The entries below enumerate all of the changes in v5 in detail along with their associated PRs. If you are migrating from v3 or v4, we recommend at least skimming the entries below to see if you're affected by the breaking changes. As always, we recommend using TypeScript with our libraries. This will be especially helpful in surfacing changes to the API which affect your usage. Even if you don't use TypeScript, you can still benefit from the typings we provide using various convenience tools like // @ts-check
(with compatible editors like VS Code).
At a higher level, the most notable changes include:
didReceiveResponse
hook.fetch
method, giving access to the full Response
objectLast-Modified
header)head
class method for issuing HEAD
requests#100 2e51657
Thanks @glasser! - Instead of memoizing GET requests forever in memory, only apply de-duplication during the lifetime of the original request. Replace the memoizeGetRequests
field with a requestDeduplicationPolicyFor()
method to determine how de-duplication works per request.
To restore the surprising infinite-unconditional-cache behavior of previous versions, use this implementation of requestDeduplicationPolicyFor()
(which replaces deduplicate-during-request-lifetime
with deduplicate-until-invalidated
):
override protected requestDeduplicationPolicyFor(
url: URL,
request: RequestOptions,
): RequestDeduplicationPolicy {
const cacheKey = this.cacheKeyFor(url, request);
if (request.method === 'GET') {
return {
policy: 'deduplicate-until-invalidated',
deduplicationKey: `${request.method} ${cacheKey}`,
};
} else {
return {
policy: 'do-not-deduplicate',
invalidateDeduplicationKeys: [`GET ${cacheKey}`],
};
}
}
To restore the behavior of memoizeGetRequests = false
, use this implementation of requestDeduplicationPolicyFor()
:
protected override requestDeduplicationPolicyFor() {
return { policy: 'do-not-deduplicate' } as const;
}
#89 4a249ec
Thanks @trevor-scheer! - This change restores the full functionality of willSendRequest
which
previously existed in the v3 version of this package. The v4 change introduced a
regression where the incoming request's body
was no longer included in the
object passed to the willSendRequest
hook, it was always undefined
.
For consistency and typings reasons, the path
argument is now the first
argument to the willSendRequest
hook, followed by the AugmentedRequest
request object.
#115 be4371f
Thanks @glasser! - The errorFromResponse
method now receives an options object with url
, request
, response
, and parsedBody
rather than just a response, and the body has already been parsed.
#110 ea43a27
Thanks @trevor-scheer! - Update default cacheKeyFor
to include method
In its previous form, cacheKeyFor
only used the URL to calculate the cache key. As a result, when cacheOptions.ttl
was specified, the method was ignored. This could lead to surprising behavior where a POST request's response was cached and returned for a GET request (for example).
The default cacheKeyFor
now includes the request method, meaning there will now be distinct cache entries for a given URL per method.
#88 2c3dbd0
Thanks @glasser! - When passing params
as an object, parameters with undefined
values are now skipped, like with JSON.stringify
. So you can write:
getUser(query: string | undefined) {
return this.get('user', { params: { query } });
}
and if query
is not provided, the query
parameter will be left off of the URL instead of given the value undefined
.
As part of this change, we've removed the ability to provide params
in formats other than this kind of object or as an URLSearchParams
object. Previously, we allowed every form of input that could be passed to new URLSearchParams()
. If you were using one of the other forms (like a pre-serialized URL string or an array of two-element arrays), just pass it directly to new URLSearchParams
; note that the feature of stripping undefined
values will not occur in this case. For example, you can replace this.get('user', { params: [['query', query]] })
with this.get('user', { params: new URLSearchParams([['query', query]]) })
. (URLSearchParams
is available in Node as a global.)
#107 4b2a6f9
Thanks @trevor-scheer! - Remove didReceiveResponse
hook
The naming of this hook is deceiving; if this hook is overridden it becomes responsible for returning the parsed body and handling errors if they occur. It was originally introduced in https://github.com/apollographql/apollo-server/issues/1324, where the author implemented it due to lack of access to the complete response (headers) in the fetch methods (get, post, ...). This approach isn't a type safe way to accomplish this and places the burden of body parsing and error handling on the user.
Removing this hook is a prerequisite to a subsequent change that will introduce the ability to fetch a complete response (headers included) aside from the provided fetch methods which only return a body. This change will reinstate the functionality that the author of this hook had originally intended in a more direct manner.
You reasonably may have used this hook for things like observability and logging,
updating response headers, or mutating the response object in some other way. If
so, you can now override the public fetch
method like so:
class MyDataSource extends RESTDataSource {
override async fetch<TResult>(
path: string,
incomingRequest: DataSourceRequest = {},
) {
const result = await super.fetch(path, incomingRequest);
// Log or update here; you have access to `result.parsedBody` and `result.response`.
// Return the `result` object when you're finished.
return result;
}
}
All of the convenience http methods (get()
, post()
, etc.) call this fetch
function, so
changes here will apply to every request that your datasource makes.
#95 c59b82f
Thanks @glasser! - Simplify interpretation of this.baseURL
so it works exactly like links in a web browser.
If you set this.baseURL
to an URL with a non-empty path component, this may change the URL that your methods talk to. Specifically:
this.get('/foo')
now replace the entire URL path from this.baseURL
. If you did not intend this, write this.get('foo')
instead.this.baseURL
has a non-empty path and does not end in a trailing slash, paths such as this.get('foo')
will replace the last component of the URL path instead of adding a new component. If you did not intend this, add a trailing slash to this.baseURL
.If you preferred the v4 semantics and do not want to make the changes described above, you can restore v4 semantics by overriding resolveURL
in your subclass with the following code from v4:
override resolveURL(path: string): ValueOrPromise<URL> {
if (path.startsWith('/')) {
path = path.slice(1);
}
const baseURL = this.baseURL;
if (baseURL) {
const normalizedBaseURL = baseURL.endsWith('/')
? baseURL
: baseURL.concat('/');
return new URL(path, normalizedBaseURL);
} else {
return new URL(path);
}
}
As part of this change, it is now possible to specify URLs whose first path segment contains a colon, such as this.get('/foo:bar')
.
#121 32f8f04
Thanks @glasser! - We now write to the shared HTTP-header-sensitive cache in the background rather than before the fetch resolves. By default, errors talking to the cache are logged with console.log
; override catchCacheWritePromiseErrors
to customize. If you call fetch()
, the result object has a httpCache.cacheWritePromise
field that you can await
if you want to know when the cache write ends.
#117 0f94ad9
Thanks @renovate! - If your provided cache
is created with PrefixingKeyValueCache.cacheDangerouslyDoesNotNeedPrefixesForIsolation
(new in @apollo/utils.keyvaluecache@2.1.0
), the httpcache:
prefix will not be added to cache keys.
#114 6ebc093
Thanks @glasser! - Allow specifying the cache key directly as a cacheKey
option in the request options. This is read by the default implementation of cacheKeyFor
(which is still called).
#106 4cbfd36
Thanks @glasser! - Previously, RESTDataSource doubled the TTL used with its shared header-sensitive cache when it may be able to use the cache entry after it goes stale because it contained the ETag
header; for these cache entries, RESTDataSource can set the If-None-Match
header when sending the REST request and the server can return a 304 response telling RESTDataSource to reuse the old response from its cache. Now, RESTDataSource also extends the TTL for responses with the Last-Modified
header (which it can validate with If-Modified-Since
).
#110 ea43a27
Thanks @trevor-scheer! - Provide head() HTTP helper method
Some REST APIs make use of HEAD requests. It seems reasonable for us to provide this method as we do the others.
It's worth noting that the API differs from the other helpers. While bodies are expected/allowed for other requests, that is explicitly not the case for HEAD requests. This method returns the request object itself rather than a parsed body so that useful information can be extracted from the headers.
#114 6ebc093
Thanks @glasser! - Allow specifying the options passed to new CachePolicy()
via a httpCacheSemanticsCachePolicyOptions
option in the request options.
#121 32f8f04
Thanks @glasser! - If you're using node-fetch
as your Fetcher implementation (the default) and the response has header names that appear multiple times (such as Set-Cookie
), then you can use the node-fetch
-specific API (await myRestDataSource.fetch(url)).response.headers.raw()
to see the multiple header values separately.
#115 be4371f
Thanks @glasser! - New throwIfResponseIsError
hook allows you to control whether a response should be returned or thrown as an error. Partially replaces the removed didReceiveResponse
hook.
#116 ac767a7
Thanks @glasser! - The cacheOptions
function and cacheOptionsFor
method may now optionally be async.
#90 b66da37
Thanks @trevor-scheer! - Add a new overridable method shouldJSONSerializeBody
for customizing body serialization behavior. This method should return a boolean
in order to inform RESTDataSource as to whether or not it should call JSON.stringify
on the request body.
#110 ea43a27
Thanks @trevor-scheer! - Add public fetch
method
Users previously had no well-defined way to access the complete response (i.e. for header inspection). The public API of HTTP helper methods only returned the parsed response body. A didReceiveResponse
hook existed as an attempt to solve this, but its semantics weren't well-defined, nor was it a type safe approach to solving the problem.
The new fetch
method allows users to "bypass" the convenience of the HTTP helpers in order to construct their own full request and inspect the complete response themselves.
The DataSourceFetchResult
type returned by this method also contains other useful information, like a requestDeduplication
field containing the request's deduplication policy and whether it was deduplicated against a previous request.
#121 609ba1f
Thanks @glasser! - When de-duplicating requests, the returned parsed body is now cloned rather than shared across duplicate requests. If you override the parseBody
method, you should also override cloneParsedBody
to match.
#105 8af22fe
Thanks @glasser! - The fetch Response now consistently has a non-empty url
property; previously, url
was an empty string if the response was read from the HTTP cache.
#90 b66da37
Thanks @trevor-scheer! - Correctly identify and serialize all plain objects (like those with a null prototype)
#94 834401d
Thanks @renovate! - Update @apollo/utils.fetcher
dependency to v2.0.0
#89 4a249ec
Thanks @trevor-scheer! - string
and Buffer
bodies are now correctly included on the outgoing request.
Due to a regression in v4, they were ignored and never sent as the body
.
string
and Buffer
bodies are now passed through to the outgoing request
(without being JSON stringified).
Changelog
4.3.2
946d79e
Thanks @trevor-scheer! - Fix build process (again), ensure built directory exists before publish