Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3ef4520
add multiple caching options
adr-iova Oct 31, 2024
d9cb707
add CI/CD pipeline
adr-iova Oct 31, 2024
714b10b
add changeset
adr-iova Oct 31, 2024
6bd5bb4
chore: version packages
github-actions[bot] Oct 31, 2024
8a96f88
Merge pull request #1 from starrett67/changeset-release/feature/aiova…
adr-iova Oct 31, 2024
d613f2b
change release flow
adr-iova Oct 31, 2024
c769f2c
Merge remote-tracking branch 'origin/feature/aiova/storage-mode' into…
adr-iova Oct 31, 2024
05dd97c
chore: release version 6.3.1
invalid-email-address Oct 31, 2024
425b70b
change release branch to main
adr-iova Oct 31, 2024
186c978
Merge remote-tracking branch 'origin/feature/aiova/storage-mode' into…
adr-iova Oct 31, 2024
7c11893
Merge pull request #2 from starrett67/feature/aiova/storage-mode
starrett67 Oct 31, 2024
13998ee
fix body already used for big responses
adr-iova Nov 1, 2024
9c37ba2
release
adr-iova Nov 1, 2024
fe1c97e
Merge pull request #3 from starrett67/feature/aiova/storage-mode
adr-iova Nov 1, 2024
2fb7cdb
test
adr-iova Nov 1, 2024
97546e8
test
adr-iova Nov 1, 2024
ba4a1ee
test
adr-iova Nov 1, 2024
b95476e
test
adr-iova Nov 1, 2024
6c09be6
chore: release version 6.3.2
invalid-email-address Nov 1, 2024
951b28e
test
adr-iova Nov 1, 2024
6875f21
Merge remote-tracking branch 'origin/feature/aiova/storage-mode' into…
adr-iova Nov 1, 2024
9b4ed27
changes for ci/cd
adr-iova Nov 1, 2024
c0aef99
Merge pull request #4 from starrett67/feature/aiova/storage-mode
adr-iova Nov 1, 2024
6759ba4
changes for ci/cd
adr-iova Nov 1, 2024
8c89148
Merge pull request #5 from starrett67/feature/aiova/storage-mode
adr-iova Nov 1, 2024
fa3d858
push back to main
adr-iova Nov 1, 2024
c568304
Merge pull request #6 from starrett67/feature/aiova/storage-mode
adr-iova Nov 1, 2024
f3978bf
fix version
adr-iova Nov 1, 2024
49b2aa9
Merge pull request #7 from starrett67/feature/aiova/storage-mode
adr-iova Nov 1, 2024
4e38bf9
chore: release version 6.3.4
invalid-email-address Nov 1, 2024
0f17507
fix: extend cache type to string and object
adr-iova Jan 14, 2025
b56d04e
Merge pull request #8 from starrett67/feature/aiova/handle-object-type
adr-iova Jan 14, 2025
baf9a32
fix: extend cache type to string and object
adr-iova Jan 14, 2025
bc93e33
Merge pull request #9 from starrett67/feature/aiova/handle-object-type
adr-iova Jan 14, 2025
6165798
chore: release version 6.4.0
invalid-email-address Jan 14, 2025
2f0e04e
fix: extend cache type to string and object
adr-iova Jan 14, 2025
aae23a3
Merge pull request #10 from starrett67/feature/aiova/handle-object-type
adr-iova Jan 14, 2025
d40bc53
chore: release version 6.5.0
invalid-email-address Jan 14, 2025
b6de2cc
fix: cache only successful responses
adr-iova Jun 20, 2025
f5fee95
fix: bump version
adr-iova Jun 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
name: Release

on:
push:
branches:
- main

jobs:
release:
name: Release
Expand All @@ -13,21 +11,43 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v4
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0

- name: Setup Node.js 16.x
uses: actions/setup-node@v4
with:
node-version: 16.x

- name: Install Dependencies
run: npm i

- name: Create Release Pull Request / NPM Publish
uses: changesets/action@v1
with:
publish: npm run changeset-publish
- name: Setup Git
run: |
git config user.name "GitHub Actions"
git config user.email "github-actions@github.com"
- name: Version and Create Tag
id: version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Run changeset version
npx changeset version

# Get new version from package.json
NEW_VERSION=$(node -p "require('./package.json').version")
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT

# Commit changes
git add .
git commit -m "chore: release version $NEW_VERSION"

# Create and push tag
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
git push origin "v$NEW_VERSION"
git push origin main
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
with:
tag_name: v${{ steps.version.outputs.new_version }}
release_name: Release v${{ steps.version.outputs.new_version }}
draft: false
prerelease: false
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# @apollo/datasource-rest

## 6.5.0

### Minor Changes

- [`2f0e04e`](https://github.com/apollographql/datasource-rest/commit/2f0e04e433556f0c5533e0699b1da2291fa93927) Thanks [@adr-iova](https://github.com/adr-iova)! - add object to cache type

## 6.4.0

### Minor Changes

- [`baf9a32`](https://github.com/apollographql/datasource-rest/commit/baf9a32e00da0a12d57915e7bb62991a90bb5b11) Thanks [@adr-iova](https://github.com/adr-iova)! - Handle objects and strings

## 6.3.4

### Patch Changes

- [`9b4ed27`](https://github.com/apollographql/datasource-rest/commit/9b4ed278111e031c3ee86672f0ecf2586b891590) Thanks [@adr-iova](https://github.com/adr-iova)! - changes for CI/CD

## 6.3.2

### Patch Changes

- [`9c37ba2`](https://github.com/apollographql/datasource-rest/commit/9c37ba20c45688d21d634003896c524707ecc5f9) Thanks [@adr-iova](https://github.com/adr-iova)! - fix body already used for big responses

## 6.3.1

### Patch Changes

- [`714b10b`](https://github.com/apollographql/datasource-rest/commit/714b10bf6b039aa39dd0d287100c4f3cea0bcd47) Thanks [@adr-iova](https://github.com/adr-iova)! - Add multiple cache strategies

## 6.3.0

### Minor Changes
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@apollo/datasource-rest",
"name": "@apollo/datasource-rest-storage",
"description": "REST DataSource for Apollo Server v4",
"version": "6.3.0",
"version": "6.5.1",
"author": "Apollo <packages@apollographql.com>",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -29,7 +29,8 @@
"watch": "tsc --build --watch",
"lint": "eslint src/**/*.ts",
"changeset-publish": "changeset publish",
"changeset-check": "changeset status --verbose --since=origin/main"
"changeset-check": "changeset status --verbose --since=origin/main",
"changeset-version": "changeset version"
},
"devDependencies": {
"@apollo/server": "4.11.0",
Expand Down
42 changes: 39 additions & 3 deletions src/HTTPCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ interface SneakyCachePolicy extends CachePolicy {
interface ResponseWithCacheWritePromise {
response: FetcherResponse;
cacheWritePromise?: Promise<void>;
parsedBody?: any;
}

export class HTTPCache<CO extends CacheOptions = CacheOptions> {
private keyValueCache: KeyValueCache<string, CO>;
private keyValueCache: KeyValueCache<string | object, CO>;
private httpFetch: Fetcher;

constructor(
keyValueCache: KeyValueCache = new InMemoryLRUCache<string, CO>(),
keyValueCache: KeyValueCache<string | object, CO> = new InMemoryLRUCache<string | object, CO>(),
httpFetch: Fetcher = nodeFetch,
) {
this.keyValueCache = new PrefixingKeyValueCache(
Expand Down Expand Up @@ -75,6 +76,41 @@ export class HTTPCache<CO extends CacheOptions = CacheOptions> {
return { response: await this.httpFetch(urlString, requestOpts) };
}

let cacheOptions = cache?.cacheOptions;
const cacheStrategy = (cacheOptions as CacheOptions)?.cacheStrategy ?? 'default';

if (cacheStrategy === 'object') {
if (requestOpts.skipCache) {
const response = await this.httpFetch(urlString, requestOpts);
const parsedBody = await response.json();
return { response, parsedBody };
}

const cachedValue = await this.keyValueCache.get(cacheKey);
if (cachedValue !== undefined) {
return {
response: new NodeFetchResponse(undefined, { status: 200 }),
parsedBody: cachedValue,
};
}

const response = await this.httpFetch(urlString, requestOpts);
const parsedBody = await response.json();

if ((cacheOptions as CacheOptions)?.ttl && response.status >= 200 && response.status <= 299) {
const cacheWritePromise = this.keyValueCache.set(
cacheKey,
parsedBody,
cacheOptions as CO
).catch(error => {
console.error('Error writing to cache:', error);
});
return { response, parsedBody, cacheWritePromise };
}

return { response, parsedBody };
}

const entry =
requestOpts.skipCache !== true
? await this.keyValueCache.get(cacheKey)
Expand All @@ -100,7 +136,7 @@ export class HTTPCache<CO extends CacheOptions = CacheOptions> {
);
}

const { policy: policyRaw, ttlOverride, body } = JSON.parse(entry);
const { policy: policyRaw, ttlOverride, body } = typeof entry === "object" ? entry : JSON.parse(entry);

const policy = CachePolicy.fromObject(policyRaw) as SneakyCachePolicy;
// Remove url from the policy, because otherwise it would never match a
Expand Down
38 changes: 26 additions & 12 deletions src/RESTDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
FetcherRequestInit,
FetcherResponse,
} from '@apollo/utils.fetcher';
import type { KeyValueCache } from '@apollo/utils.keyvaluecache';
import type { KeyValueCache, KeyValueCacheSetOptions } from '@apollo/utils.keyvaluecache';
import type { Logger } from '@apollo/utils.logger';
import type { WithRequired } from '@apollo/utils.withrequired';
import { GraphQLError } from 'graphql';
Expand Down Expand Up @@ -136,12 +136,17 @@ export interface CacheOptions {
* cached.
*/
ttl?: number;
/**
* default - the default cache strategy that will stringify/parse the response
* object - will cache the response in the original format
*/
cacheStrategy?: 'default' | 'object';
}

const NODE_ENV = process.env.NODE_ENV;

export interface DataSourceConfig {
cache?: KeyValueCache;
cache?: KeyValueCache<string | object, KeyValueCacheSetOptions>;
fetch?: Fetcher;
logger?: Logger;
}
Expand Down Expand Up @@ -323,17 +328,22 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
'requestDeduplication'
>,
requestDeduplicationResult: RequestDeduplicationResult,
cacheOptions: CacheOptions
): DataSourceFetchResult<TResult> {
return {
...dataSourceFetchResult,
requestDeduplication: requestDeduplicationResult,
parsedBody: this.cloneParsedBody(dataSourceFetchResult.parsedBody),
parsedBody: this.cloneParsedBody(dataSourceFetchResult.parsedBody, cacheOptions),
};
}

protected cloneParsedBody<TResult>(parsedBody: TResult) {
// consider using `structuredClone()` when we drop support for Node 16
return cloneDeep(parsedBody);
protected cloneParsedBody<TResult>(parsedBody: TResult, cacheOptions: CacheOptions) {
const cacheStrategy = cacheOptions?.cacheStrategy
if(cacheStrategy === "object") {
return (Array.isArray(parsedBody) ? [...parsedBody] : {...parsedBody}) as TResult
} else {
return cloneDeep(parsedBody);
}
}

protected shouldJSONSerializeBody(
Expand Down Expand Up @@ -536,7 +546,7 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
? outgoingRequest.cacheOptions
: this.cacheOptionsFor?.bind(this);
try {
const { response, cacheWritePromise } = await this.httpCache.fetch(
const { response, cacheWritePromise, parsedBody } = await this.httpCache.fetch(
url,
outgoingRequest,
{
Expand All @@ -551,17 +561,18 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
this.catchCacheWritePromiseErrors(cacheWritePromise);
}

const parsedBody = await this.parseBody(response);
const _parsedBody = parsedBody ?? await this.parseBody(response);

await this.throwIfResponseIsError({
url,
request: outgoingRequest,
response,
parsedBody,
// @ts-ignore
_parsedBody,
});

return {
parsedBody: parsedBody as any as TResult,
parsedBody: _parsedBody as any as TResult,
response,
httpCache: {
cacheWritePromise,
Expand All @@ -574,6 +585,9 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
});
};

const cacheOptions = outgoingRequest.cacheOptions
? outgoingRequest.cacheOptions
: this.cacheOptionsFor?.bind(this);
// Cache GET requests based on the calculated cache key
// Disabling the request cache does not disable the response cache
const policy = this.requestDeduplicationPolicyFor(url, outgoingRequest);
Expand All @@ -589,7 +603,7 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
this.cloneDataSourceFetchResult(result, {
policy,
deduplicatedAgainstPreviousRequest: true,
}),
}, cacheOptions as CacheOptions),
);

const thisRequestPromise = performRequest();
Expand All @@ -610,7 +624,7 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
return this.cloneDataSourceFetchResult(await thisRequestPromise, {
policy,
deduplicatedAgainstPreviousRequest: false,
});
}, cacheOptions as CacheOptions);
} finally {
if (policy.policy === 'deduplicate-during-request-lifetime') {
this.deduplicationPromises.delete(policy.deduplicationKey);
Expand Down