Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

Move populateExchange to a package (#567)

* start moving it out, extract common exchange helpers to commons folder, ...

* correct graphcache imports related to common ast and help

* cleanup and lint

* move shared to a package in /exchanges

* add version to pkg.json

* run linter

* add jest preset to shared

* correct the jest regex

* try adding it to tsconfig

* add build to shared

* make the build work by adding it to the tsconfig rootDirs

* remove gitignore and add readme

* fix import and set version to 0.1.0 for populate

-1
exchanges/graphcache/src/ast/index.ts
···
export { getFieldArguments, normalizeVariables } from './variables';
export * from './traversal';
-
export * from './node';
export * from './schemaPredicates';
+1 -1
exchanges/graphcache/src/ast/node.test.ts exchanges/shared/src/ast/node.test.ts
···
-
import { unwrapType } from './';
+
import { unwrapType } from './node';
import { GraphQLScalarType, GraphQLNonNull, GraphQLList } from 'graphql';
describe('unwrapType', () => {
+3 -1
exchanges/graphcache/src/ast/node.ts exchanges/shared/src/ast/node.ts
···
GraphQLOutputType,
Kind,
isWrappingType,
+
GraphQLWrappingType,
} from 'graphql';
-
import { SelectionSet, GraphQLFlatType } from '../types';
+
export type SelectionSet = ReadonlyArray<SelectionNode>;
+
export type GraphQLFlatType = Exclude<GraphQLOutputType, GraphQLWrappingType>;
/** Returns the name of a given node */
export const getName = (node: { name: NameNode }): string => node.name.value;
+1 -1
exchanges/graphcache/src/ast/schemaPredicates.ts
···
GraphQLUnionType,
} from 'graphql';
-
import { invariant, warn } from '../helpers/help';
+
import { warn, invariant } from 'shared';
export const isFieldNullable = (
schema: GraphQLSchema,
+2 -1
exchanges/graphcache/src/ast/traversal.test.ts
···
import gql from 'graphql-tag';
-
import { getSelectionSet } from './node';
+
import { getSelectionSet } from 'shared';
+
import { getMainOperation, shouldInclude } from './traversal';
describe('getMainOperation', () => {
+1 -2
exchanges/graphcache/src/ast/traversal.ts
···
valueFromASTUntyped,
Kind,
} from 'graphql';
+
import { invariant, getName } from 'shared';
-
import { invariant } from '../helpers/help';
-
import { getName } from './node';
import { Fragments, Variables } from '../types';
const isFragmentNode = (node: DefinitionNode): node is FragmentDefinitionNode =>
+1 -1
exchanges/graphcache/src/ast/variables.ts
···
OperationDefinitionNode,
valueFromASTUntyped,
} from 'graphql';
+
import { getName } from 'shared';
-
import { getName } from './node';
import { makeDict } from '../helpers/dict';
import { Variables } from '../types';
+20 -1
exchanges/graphcache/src/helpers/help.ts exchanges/shared/src/helpers/help.ts
···
// You can read more about the messages themselves in `docs/help.md`
import { Kind, ExecutableDefinitionNode, InlineFragmentNode } from 'graphql';
-
import { ErrorCode } from '../types';
+
+
export type ErrorCode =
+
| 1
+
| 2
+
| 3
+
| 4
+
| 5
+
| 6
+
| 7
+
| 8
+
| 9
+
| 10
+
| 11
+
| 12
+
| 13
+
| 15
+
| 16
+
| 17
+
| 18
+
| 19;
type DebugNode = ExecutableDefinitionNode | InlineFragmentNode;
+1 -1
exchanges/graphcache/src/index.ts
···
export * from './types';
export { Store, initDataState, clearDataState } from './store';
export { cacheExchange } from './cacheExchange';
-
export { populateExchange } from './populateExchange';
+
export { populateExchange } from '@urql/exchange-populate';
+2 -9
exchanges/graphcache/src/operations/invalidate.ts
···
import { FieldNode } from 'graphql';
+
import { getSelectionSet, getName, SelectionSet } from 'shared';
import {
getMainOperation,
normalizeVariables,
getFragments,
-
getSelectionSet,
-
getName,
getFieldArguments,
} from '../ast';
-
import {
-
EntityField,
-
OperationRequest,
-
Variables,
-
Fragments,
-
SelectionSet,
-
} from '../types';
+
import { EntityField, OperationRequest, Variables, Fragments } from '../types';
import * as InMemoryData from '../store/data';
import { Store, keyOfField } from '../store';
+9 -6
exchanges/graphcache/src/operations/query.ts
···
import { FieldNode, DocumentNode, FragmentDefinitionNode } from 'graphql';
+
import {
+
getSelectionSet,
+
getName,
+
SelectionSet,
+
getFragmentTypeName,
+
getFieldAlias,
+
warn,
+
pushDebugNode,
+
} from 'shared';
import {
getFragments,
getMainOperation,
-
getSelectionSet,
normalizeVariables,
-
getName,
getFieldArguments,
-
getFieldAlias,
-
getFragmentTypeName,
} from '../ast';
import {
···
Data,
DataField,
Link,
-
SelectionSet,
OperationRequest,
NullArray,
} from '../types';
···
import * as InMemoryData from '../store/data';
import { makeDict } from '../helpers/dict';
-
import { warn, pushDebugNode } from '../helpers/help';
import { SelectionIterator, ensureData } from './shared';
import {
+12 -19
exchanges/graphcache/src/operations/shared.ts
···
import { FieldNode, InlineFragmentNode, FragmentDefinitionNode } from 'graphql';
+
import {
+
warn,
+
pushDebugNode,
+
isInlineFragment,
+
getTypeCondition,
+
getSelectionSet,
+
getName,
+
SelectionSet,
+
isFieldNode,
+
} from 'shared';
-
import { warn, pushDebugNode } from '../helpers/help';
import { hasField } from '../store/data';
import { Store, keyOfField } from '../store';
-
import {
-
Fragments,
-
Variables,
-
SelectionSet,
-
DataField,
-
NullArray,
-
Data,
-
} from '../types';
+
import { Fragments, Variables, DataField, NullArray, Data } from '../types';
-
import {
-
getTypeCondition,
-
getFieldArguments,
-
shouldInclude,
-
isInterfaceOfType,
-
isFieldNode,
-
isInlineFragment,
-
getSelectionSet,
-
getName,
-
} from '../ast';
+
import { getFieldArguments, shouldInclude, isInterfaceOfType } from '../ast';
interface Context {
store: Store;
+10 -6
exchanges/graphcache/src/operations/write.ts
···
import { FieldNode, DocumentNode, FragmentDefinitionNode } from 'graphql';
import {
-
getFieldAlias,
getFragments,
getMainOperation,
-
getSelectionSet,
normalizeVariables,
-
getFragmentTypeName,
-
getName,
getFieldArguments,
isFieldAvailableOnType,
} from '../ast';
+
import {
+
getSelectionSet,
+
getName,
+
SelectionSet,
+
getFragmentTypeName,
+
getFieldAlias,
+
invariant,
+
warn,
+
pushDebugNode,
+
} from 'shared';
import {
NullArray,
···
Variables,
Data,
Link,
-
SelectionSet,
OperationRequest,
} from '../types';
···
import * as InMemoryData from '../store/data';
import { makeDict } from '../helpers/dict';
-
import { invariant, warn, pushDebugNode } from '../helpers/help';
import { SelectionIterator, ensureData } from './shared';
export interface WriteResult {
exchanges/graphcache/src/populateExchange.test.ts exchanges/populate/src/populateExchange.test.ts
+3 -4
exchanges/graphcache/src/populateExchange.ts exchanges/populate/src/populateExchange.ts
···
Kind,
visit,
} from 'graphql';
+
import { getName, getSelectionSet, unwrapType, invariant, warn } from 'shared';
import { pipe, tap, map } from 'wonka';
import { Exchange, Operation } from '@urql/core';
-
import { getName, getSelectionSet, unwrapType } from './ast';
-
import { makeDict } from './helpers/dict';
-
import { invariant, warn } from './helpers/help';
-
interface PopulateExchangeOpts {
schema: IntrospectionQuery;
}
+
+
const makeDict = (): any => Object.create(null);
/** An exchange for auto-populating mutations with a required response body. */
export const populateExchange = ({
+1 -1
exchanges/graphcache/src/store/data.ts
···
} from '../types';
import { makeDict } from '../helpers/dict';
-
import { invariant, currentDebugStack } from '../helpers/help';
+
import { invariant, currentDebugStack } from 'shared';
import { fieldInfoOfKey, joinKeys, prefixKey } from './keys';
import { defer } from './timing';
+1 -1
exchanges/graphcache/src/store/store.ts
···
KeyingConfig,
DataFields,
} from '../types';
-
import { invariant } from '../helpers/help';
+
import { invariant } from 'shared';
import { read, readFragment } from '../operations/query';
import { writeFragment, startWrite } from '../operations/write';
+1 -30
exchanges/graphcache/src/types.ts
···
-
import {
-
DocumentNode,
-
FragmentDefinitionNode,
-
SelectionNode,
-
GraphQLOutputType,
-
GraphQLWrappingType,
-
} from 'graphql';
+
import { DocumentNode, FragmentDefinitionNode } from 'graphql';
// Helper types
export type NullArray<T> = Array<null | T>;
-
// GraphQL helper types
-
export type SelectionSet = ReadonlyArray<SelectionNode>;
-
export type GraphQLFlatType = Exclude<GraphQLOutputType, GraphQLWrappingType>;
export interface Fragments {
[fragmentName: string]: void | FragmentDefinitionNode;
}
···
read(): Promise<SerializedEntries>;
write(data: SerializedEntries): Promise<void>;
}
-
-
export type ErrorCode =
-
| 1
-
| 2
-
| 3
-
| 4
-
| 5
-
| 6
-
| 7
-
| 8
-
| 9
-
| 10
-
| 11
-
| 12
-
| 13
-
| 15
-
| 16
-
| 17
-
| 18
-
| 19;
+3 -1
exchanges/graphcache/tsconfig.json
···
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
+
"rootDirs": ["./", "../shared", "../populate"],
"baseUrl": "./",
"paths": {
"urql": ["../../node_modules/urql/src"],
"*-urql": ["../../node_modules/*-urql/src"],
-
"@urql/*": ["../../node_modules/@urql/*/src"]
+
"@urql/*": ["../../node_modules/@urql/*/src"],
+
"shared": ["../../node_modules/shared/src"]
}
}
}
+7
exchanges/populate/CHANGELOG.md
···
+
# @urql/exchange-populate
+
+
## 0.1.0
+
+
### Initial release
+
+
- Moved the `populateExchange` from `@urql/exchange-graphcache` to its own package.
+77
exchanges/populate/README.md
···
+
# @urql/exchange-populate
+
+
`populate` is an exchange for auto-populating fields in your mutations.
+
+
## Quick Start Guide
+
+
First install `@urql/exchange-populate` alongside `urql`:
+
+
```sh
+
yarn add @urql/exchange-populate
+
# or
+
npm install --save @urql/exchange-populate
+
```
+
+
You'll then need to add the `populateExchange`, that this package exposes.
+
+
```js
+
import { createClient, dedupExchange, cacheExchange, fetchExchange } from 'urql';
+
import { populateExchange } from '@urql/exchange-populate';
+
+
const client = createClient({
+
url: 'http://localhost:1234/graphql',
+
exchanges: [
+
dedupExchange,
+
populateExchange,
+
cacheExchange,
+
fetchExchange,
+
],
+
});
+
```
+
+
## Example usage
+
+
Consider the following queries which have been requested in other parts of your application:
+
+
+
```graphql
+
# Query 1
+
{
+
todos {
+
id
+
name
+
}
+
}
+
+
# Query 2
+
{
+
todos {
+
id
+
createdAt
+
}
+
}
+
```
+
+
Without the `populateExchange` you may write a mutation like the following which returns a newly created todo item:
+
+
```graphql
+
# Without populate
+
mutation addTodo(id: ID!) {
+
addTodo(id: $id) {
+
id # To update Query 1 & 2
+
name # To update Query 1
+
createdAt # To update Query 2
+
}
+
}
+
```
+
+
By using `populateExchange`, you no longer need to manually specify the selection set required to update your other queries. Instead you can just add the `@populate` directive.
+
+
```graphql
+
# With populate
+
mutation addTodo(id: ID!) {
+
addTodo(id: $id) @populate
+
}
+
```
+
+
> Note: The above two mutations produce an identical GraphQL request.
+61
exchanges/populate/package.json
···
+
{
+
"name": "@urql/exchange-populate",
+
"version": "0.1.0",
+
"description": "An exchange that automaticcally populates the mutation selection body",
+
"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/populate"
+
},
+
"keywords": [
+
"urql",
+
"formidablelabs",
+
"exchanges"
+
],
+
"main": "dist/urql-exchange-populate.cjs.js",
+
"module": "dist/urql-exchange-populate.esm.js",
+
"types": "dist/types/index.d.ts",
+
"source": "src/index.ts",
+
"exports": {
+
".": {
+
"import": "dist/urql-exchange-populate.esm.js",
+
"require": "dist/urql-exchange-populate.cjs.js",
+
"types": "dist/types/index.d.ts",
+
"source": "src/index.ts"
+
}
+
},
+
"files": [
+
"LICENSE",
+
"CHANGELOG.md",
+
"README.md",
+
"dist/",
+
"extras/"
+
],
+
"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": "../../scripts/prepare/index.js",
+
"prepublishOnly": "run-s clean test build"
+
},
+
"jest": {
+
"preset": "../../scripts/jest/preset"
+
},
+
"dependencies": {
+
"wonka": "^3.2.1 || ^4.0.0",
+
"@urql/core": ">=1.9.0"
+
},
+
"peerDependencies": {
+
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
+
},
+
"devDependencies": {
+
"graphql": "^14.5.8",
+
"graphql-tag": "^2.10.1"
+
}
+
}
+1
exchanges/populate/src/index.ts
···
+
export * from './populateExchange';
+14
exchanges/populate/tsconfig.json
···
+
{
+
"extends": "../../tsconfig.json",
+
"include": ["src"],
+
"compilerOptions": {
+
"baseUrl": "./",
+
"rootDirs": ["./", "../shared", "../populate"],
+
"paths": {
+
"urql": ["../../node_modules/urql/src"],
+
"*-urql": ["../../node_modules/*-urql/src"],
+
"@urql/*": ["../../node_modules/@urql/*/src"],
+
"shared": ["../../node_modules/shared/src"]
+
}
+
}
+
}
+11
exchanges/shared/package.json
···
+
{
+
"private": true,
+
"name": "shared",
+
"version": "1.0.0",
+
"scripts": {
+
"build": "echo 'not building shared'"
+
},
+
"jest": {
+
"preset": "../../scripts/jest/preset"
+
}
+
}
+1
exchanges/shared/src/ast/index.ts
···
+
export * from './node';
+1
exchanges/shared/src/helpers/index.ts
···
+
export * from './help';
+2
exchanges/shared/src/index.ts
···
+
export * from './ast';
+
export * from './helpers';
+12
exchanges/shared/tsconfig.json
···
+
{
+
"extends": "../../tsconfig.json",
+
"include": ["src"],
+
"compilerOptions": {
+
"baseUrl": "./",
+
"paths": {
+
"urql": ["../../node_modules/urql/src"],
+
"*-urql": ["../../node_modules/*-urql/src"],
+
"@urql/*": ["../../node_modules/@urql/*/src"]
+
}
+
}
+
}
+1
scripts/jest/preset.js
···
"^urql$": "<rootDir>/../../node_modules/urql/src",
"^(.*-urql)$": "<rootDir>/../../node_modules/$1/src",
"^@urql/(.*)$": "<rootDir>/../../node_modules/@urql/$1/src",
+
"^shared$": "<rootDir>/../../node_modules/shared/src",
},
watchPlugins: ['jest-watch-yarn-workspaces'],
testRegex: '(src/.*(\\.|/)(test|spec))\\.tsx?$',
+2 -1
tsconfig.json
···
"urql": ["packages/react-urql/src"],
"*-urql": ["packages/*-urql/src"],
"@urql/exchange-*": ["exchanges/*/src"],
-
"@urql/*": ["packages/*-urql/src", "packages/*/src"]
+
"@urql/*": ["packages/*-urql/src", "packages/*/src"],
+
"shared": ["exchanges/shared/src"]
},
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,