-
-
Notifications
You must be signed in to change notification settings - Fork 454
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(toe): Add @urql/exchange-throw-on-error
- Loading branch information
Showing
9 changed files
with
404 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# @urql/exchange-throw-on-error (Exchange factory) | ||
|
||
`@urql/exchange-throw-on-error` is an exchange for the [`urql`](../../README.md) GraphQL client that makes field access to data throw an error if the field was errored. | ||
|
||
It is built on top of the [`graphql-toe`](https://github.com/graphile/graphql-toe) package. | ||
|
||
## Quick Start Guide | ||
|
||
First install `@urql/exchange-throw-on-error` alongside `urql`: | ||
|
||
```sh | ||
yarn add @urql/exchange-throw-on-error | ||
# or | ||
npm install --save @urql/exchange-throw-on-error | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "@urql/exchange-throw-on-error", | ||
"version": "0.0.0", | ||
"exports": { | ||
".": "./src/index.ts" | ||
}, | ||
"exclude": [ | ||
"node_modules", | ||
"cypress", | ||
"**/*.test.*", | ||
"**/*.spec.*", | ||
"**/*.test.*.snap", | ||
"**/*.spec.*.snap" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"name": "@urql/exchange-throw-on-error", | ||
"version": "0.0.0", | ||
"description": "An exchange for throw-on-error support in urql", | ||
"sideEffects": false, | ||
"homepage": "https://formidable.com/open-source/urql/docs/", | ||
"bugs": "https://github.com/urql-graphql/urql/issues", | ||
"license": "MIT", | ||
"author": "urql GraphQL Contributors", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/urql-graphql/urql.git", | ||
"directory": "exchanges/throw-on-error" | ||
}, | ||
"keywords": [ | ||
"urql", | ||
"graphql client", | ||
"graphql", | ||
"exchanges", | ||
"throw on error" | ||
], | ||
"main": "dist/urql-exchange-throw-on-error", | ||
"module": "dist/urql-exchange-throw-on-error.mjs", | ||
"types": "dist/urql-exchange-throw-on-error.d.ts", | ||
"source": "src/index.ts", | ||
"exports": { | ||
".": { | ||
"types": "./dist/urql-exchange-throw-on-error.d.ts", | ||
"import": "./dist/urql-exchange-throw-on-error.mjs", | ||
"require": "./dist/urql-exchange-throw-on-error.js", | ||
"source": "./src/index.ts" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"files": [ | ||
"LICENSE", | ||
"CHANGELOG.md", | ||
"README.md", | ||
"dist/" | ||
], | ||
"scripts": { | ||
"test": "vitest", | ||
"clean": "rimraf dist", | ||
"check": "tsc --noEmit", | ||
"lint": "eslint --ext=js,jsx,ts,tsx .", | ||
"build": "rollup -c ../../scripts/rollup/config.mjs", | ||
"prepare": "node ../../scripts/prepare/index.js", | ||
"prepublishOnly": "run-s clean build" | ||
}, | ||
"devDependencies": { | ||
"@urql/core": "workspace:*", | ||
"graphql": "^16.0.0" | ||
}, | ||
"peerDependencies": { | ||
"@urql/core": "^5.0.0" | ||
}, | ||
"dependencies": { | ||
"@urql/core": "^5.0.0", | ||
"graphql-toe": "^0.1.3", | ||
"wonka": "^6.3.2" | ||
}, | ||
"publishConfig": { | ||
"access": "public", | ||
"provenance": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { throwOnErrorExchange } from './throwOnErrorExchange'; |
259 changes: 259 additions & 0 deletions
259
exchanges/throw-on-error/src/throwOnErrorExchange.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
import { pipe, map, fromValue, toPromise, take } from 'wonka'; | ||
import { vi, expect, it, beforeEach } from 'vitest'; | ||
import { GraphQLError } from 'graphql'; | ||
|
||
import { | ||
gql, | ||
createClient, | ||
Operation, | ||
ExchangeIO, | ||
Client, | ||
CombinedError, | ||
} from '@urql/core'; | ||
|
||
import { throwOnErrorExchange } from './throwOnErrorExchange'; | ||
|
||
const dispatchDebug = vi.fn(); | ||
|
||
const query = gql` | ||
{ | ||
topLevel | ||
topLevelList | ||
object { | ||
inner | ||
} | ||
objectList { | ||
inner | ||
} | ||
} | ||
`; | ||
const mockData = { | ||
topLevel: 'topLevel', | ||
topLevelList: ['topLevelList'], | ||
object: { inner: 'inner' }, | ||
objectList: [{ inner: 'inner' }], | ||
}; | ||
|
||
let client: Client, op: Operation; | ||
beforeEach(() => { | ||
client = createClient({ | ||
url: 'http://0.0.0.0', | ||
exchanges: [], | ||
}); | ||
op = client.createRequestOperation('query', { key: 1, query, variables: {} }); | ||
}); | ||
|
||
it('throws on top level field error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
topLevel: null, | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('top level error', { path: ['topLevel'] }), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.topLevel).toThrow('top level error'); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.topLevelList[0]).not.toThrow(); | ||
}); | ||
|
||
it('throws on top level list element error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
topLevelList: ['topLevelList', null], | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('top level list error', { | ||
path: ['topLevelList', 1], | ||
}), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.topLevelList[1]).toThrow('top level list error'); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.topLevelList[0]).not.toThrow(); | ||
}); | ||
|
||
it('throws on object field error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
object: null, | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('object field error', { path: ['object'] }), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.object).toThrow('object field error'); | ||
expect(() => res.data?.object.inner).toThrow('object field error'); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.topLevel).not.toThrow(); | ||
}); | ||
|
||
it('throws on object inner field error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
object: { | ||
inner: null, | ||
}, | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('object inner field error', { | ||
path: ['object', 'inner'], | ||
}), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.object.inner).toThrow('object inner field error'); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.object).not.toThrow(); | ||
}); | ||
|
||
it('throws on object list field error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
objectList: null, | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('object list field error', { | ||
path: ['objectList'], | ||
}), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.objectList).toThrow('object list field error'); | ||
expect(() => res.data?.objectList[0]).toThrow('object list field error'); | ||
expect(() => res.data?.objectList[0].inner).toThrow( | ||
'object list field error' | ||
); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.topLevel).not.toThrow(); | ||
}); | ||
|
||
it('throws on object inner field error', async () => { | ||
const forward: ExchangeIO = ops$ => | ||
pipe( | ||
ops$, | ||
map( | ||
operation => | ||
({ | ||
operation, | ||
data: { | ||
...mockData, | ||
objectList: [{ inner: 'inner' }, { inner: null }], | ||
}, | ||
error: new CombinedError({ | ||
graphQLErrors: [ | ||
new GraphQLError('object list inner field error', { | ||
path: ['objectList', 1, 'inner'], | ||
}), | ||
], | ||
}), | ||
}) as any | ||
) | ||
); | ||
|
||
const res = await pipe( | ||
fromValue(op), | ||
throwOnErrorExchange()({ forward, client, dispatchDebug }), | ||
take(1), | ||
toPromise | ||
); | ||
|
||
expect(() => res.data?.objectList[1].inner).toThrow( | ||
'object list inner field error' | ||
); | ||
expect(() => res.data).not.toThrow(); | ||
expect(() => res.data?.objectList[0].inner).not.toThrow(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import type { Exchange } from '@urql/core'; | ||
import { mapExchange } from '@urql/core'; | ||
import { toe } from 'graphql-toe'; | ||
|
||
/** Exchange factory that maps the fields of the data to throw an error on access if the field was errored. | ||
* | ||
* @returns the created throw-on-error {@link Exchange}. | ||
*/ | ||
export const throwOnErrorExchange = (): Exchange => { | ||
return mapExchange({ | ||
onResult(result) { | ||
if (result.data) { | ||
const errors = result.error && result.error.graphQLErrors; | ||
result.data = toe({ data: result.data, errors }); | ||
} | ||
return result; | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"include": ["src"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { mergeConfig } from 'vitest/config'; | ||
import baseConfig from '../../vitest.config'; | ||
|
||
export default mergeConfig(baseConfig, {}); |
Oops, something went wrong.