Skip to content

Commit

Permalink
feat(set-context): create set context exchange (#2610)
Browse files Browse the repository at this point in the history
* create set context exchange

* typo

* add tests

* Update exchanges/context/README.md
  • Loading branch information
JoviDeCroock authored Aug 19, 2022
1 parent c5c6cf0 commit e79b8ab
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 0 deletions.
5 changes: 5 additions & 0 deletions exchanges/context/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## v0.1.0

**Initial Release**
41 changes: 41 additions & 0 deletions exchanges/context/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<h2 align="center">@urql/exchange-context</h2>

<p align="center"><strong>An exchange for setting operation context in <code>urql</code></strong></p>

`@urql/exchange-context` is an exchange for the [`urql`](https://github.com/FormidableLabs/urql) GraphQL client which can set the operation context both synchronously as well as asynchronously

## Quick Start Guide

First install `@urql/exchange-context` alongside `urql`:

```sh
yarn add @urql/exchange-context
# or
npm install --save @urql/exchange-context
```

You'll then need to add the `contextExchange`, that this package exposes, to your `urql` Client, the positioning of this exchange depends on whether you set an async setter or not. If you set an async context-setter it's best placed after all the synchronous exchanges (in front of the fetchExchange).

```js
import { createClient, dedupExchange, cacheExchange, fetchExchange } from 'urql';
import { contextExchange } from '@urql/exchange-context';

const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
dedupExchange,
cacheExchange,
contextExchange({
getContext: async (operation) => {
const token = await getToken();
return { ...operation.context, headers: { authorization: token } }
},
}),
fetchExchange,
],
});
```

## Maintenance Status

**Active:** Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.
65 changes: 65 additions & 0 deletions exchanges/context/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@urql/exchange-context",
"version": "0.1.0",
"description": "An exchange for setting (a)synchronous operation-context in urql",
"sideEffects": false,
"homepage": "https://formidable.com/open-source/urql/docs/",
"bugs": "https://github.com/FormidableLabs/urql/issues",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/FormidableLabs/urql.git",
"directory": "exchanges/context"
},
"keywords": [
"urql",
"exchange",
"context",
"formidablelabs",
"exchanges"
],
"main": "dist/urql-exchange-context",
"module": "dist/urql-exchange-context.mjs",
"types": "dist/types/index.d.ts",
"source": "src/index.ts",
"exports": {
".": {
"import": "./dist/urql-exchange-context.mjs",
"require": "./dist/urql-exchange-context.js",
"types": "./dist/types/index.d.ts",
"source": "./src/index.ts"
},
"./package.json": "./package.json"
},
"files": [
"LICENSE",
"CHANGELOG.md",
"README.md",
"dist/"
],
"scripts": {
"test": "jest",
"clean": "rimraf dist extras",
"check": "tsc --noEmit",
"lint": "eslint --ext=js,jsx,ts,tsx .",
"build": "rollup -c ../../scripts/rollup/config.js",
"prepare": "node ../../scripts/prepare/index.js",
"prepublishOnly": "run-s clean build"
},
"jest": {
"preset": "../../scripts/jest/preset"
},
"dependencies": {
"@urql/core": ">=2.3.6",
"wonka": "^6.0.0"
},
"peerDependencies": {
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"devDependencies": {
"graphql": "^16.0.0"
},
"publishConfig": {
"access": "public"
}
}
117 changes: 117 additions & 0 deletions exchanges/context/src/context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { pipe, map, makeSubject, publish, tap } from 'wonka';

import {
gql,
createClient,
Operation,
OperationResult,
ExchangeIO,
} from '@urql/core';

import { contextExchange } from './context';

const queryOne = gql`
{
author {
id
name
}
}
`;

const queryOneData = {
__typename: 'Query',
author: {
__typename: 'Author',
id: '123',
name: 'Author',
},
};

const dispatchDebug = jest.fn();
let client, op, ops$, next;
beforeEach(() => {
client = createClient({ url: 'http://0.0.0.0' });
op = client.createRequestOperation('query', {
key: 1,
query: queryOne,
});

({ source: ops$, next } = makeSubject<Operation>());
});

it(`calls getContext`, () => {
const response = jest.fn(
(forwardOp: Operation): OperationResult => {
return {
operation: forwardOp,
data: queryOneData,
};
}
);

const result = jest.fn();
const forward: ExchangeIO = ops$ => {
return pipe(ops$, map(response));
};

const headers = { hello: 'world' };
pipe(
contextExchange({
getContext: op => ({ ...op.context, headers }),
})({
forward,
client,
dispatchDebug,
})(ops$),
tap(result),
publish
);

next(op);

expect(response).toHaveBeenCalledTimes(1);
expect(response.mock.calls[0][0].context.headers).toEqual(headers);
expect(result).toHaveBeenCalledTimes(1);
});

it(`calls getContext async`, done => {
const response = jest.fn(
(forwardOp: Operation): OperationResult => {
return {
operation: forwardOp,
data: queryOneData,
};
}
);

const result = jest.fn();
const forward: ExchangeIO = ops$ => {
return pipe(ops$, map(response));
};

const headers = { hello: 'world' };
pipe(
contextExchange({
getContext: async op => {
await Promise.resolve();
return { ...op.context, headers };
},
})({
forward,
client,
dispatchDebug,
})(ops$),
tap(result),
publish
);

next(op);

setTimeout(() => {
expect(response).toHaveBeenCalledTimes(1);
expect(response.mock.calls[0][0].context.headers).toEqual(headers);
expect(result).toHaveBeenCalledTimes(1);
done();
}, 10);
});
39 changes: 39 additions & 0 deletions exchanges/context/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Exchange,
makeOperation,
Operation,
OperationContext,
} from '@urql/core';
import { fromPromise, fromValue, mergeMap, pipe } from 'wonka';

export interface ContextExchangeArgs {
getContext: (
operation: Operation
) => OperationContext | Promise<OperationContext>;
}

export const contextExchange = ({
getContext,
}: ContextExchangeArgs): Exchange => ({ forward }) => {
return ops$ => {
return pipe(
ops$,
mergeMap(operation => {
const result = getContext(operation);
const isPromise = 'then' in result;
if (isPromise) {
return fromPromise(
result.then((ctx: OperationContext) =>
makeOperation(operation.kind, operation, ctx)
)
);
} else {
return fromValue(
makeOperation(operation.kind, operation, result as OperationContext)
);
}
}),
forward
);
};
};
1 change: 1 addition & 0 deletions exchanges/context/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { contextExchange, ContextExchangeArgs } from './context';
13 changes: 13 additions & 0 deletions exchanges/context/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "./",
"paths": {
"urql": ["../../node_modules/urql/src"],
"*-urql": ["../../node_modules/*-urql/src"],
"@urql/core/*": ["../../node_modules/@urql/core/src/*"],
"@urql/*": ["../../node_modules/@urql/*/src"]
}
}
}

0 comments on commit e79b8ab

Please sign in to comment.