diff --git a/docs/api.md b/docs/api.md index 3ab2e460d6..c610c7b01a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -209,6 +209,14 @@ Internally they then create an `Operation` and call `.executeRequestOperation()` the `Operation`. This then returns a `Source`, i.e. a stream of `OperationResult`s. +#### .query and .mutation + +These two methods accept a `query`, `variables` and a `context`, these two methods +are really similar to the above in the sense that they return you a `Source` +you can subscribe to. The difference is that this returned value has a method on it called +`toPromise`, when invoked it will convert the `Source` to a one-time promise. These methods +are ideal for SSR, like for example the `getInitialProps` method in [Next.js](https://nextjs.org/). + #### .executeRequestOperation() This method accepts an `Operation` and handles the flow of said `Operation`. Every `Operation` diff --git a/src/client.test.ts b/src/client.test.ts index 6bc46e00d2..3f79dcbdfe 100755 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -58,12 +58,71 @@ describe('exchange args', () => { expect(typeof exchangeMock.mock.calls[0][0].forward).toBe('function'); }); - it('recieves client', () => { + it('receives client', () => { // @ts-ignore expect(exchangeMock.mock.calls[0][0]).toHaveProperty('client', client); }); }); +describe('promisified arguments', () => { + it('query', () => { + const queryResult = client + .query( + gql` + { + todos { + id + } + } + `, + { example: 1234 }, + {} + ) + .toPromise(); + + const received = receivedOps[0]; + expect(print(received.query)).toEqual(print(query.query)); + expect(received.key).toBeDefined(); + expect(received.variables).toEqual({ example: 1234 }); + expect(received.operationName).toEqual('query'); + expect(received.context).toEqual({ + url: 'https://hostname.com', + requestPolicy: 'cache-and-network', + fetchOptions: undefined, + fetch: undefined, + }); + expect(queryResult).toHaveProperty('then'); + }); + + it('mutation', () => { + const mutationResult = client + .mutation( + gql` + { + todos { + id + } + } + `, + { example: 1234 } + ) + .toPromise(); + + const received = receivedOps[0]; + expect(print(received.query)).toEqual(print(query.query)); + expect(received.key).toBeDefined(); + expect(received.variables).toEqual({ example: 1234 }); + expect(received.operationName).toEqual('mutation'); + expect(received.context).toEqual({ + url: 'https://hostname.com', + requestPolicy: 'cache-and-network', + fetchOptions: undefined, + fetch: undefined, + }); + expect(mutationResult).toHaveProperty('then'); + }); +}); + describe('executeQuery', () => { it('passes query string exchange', () => { pipe( diff --git a/src/client.ts b/src/client.ts index 970d3d7488..aac08bda08 100755 --- a/src/client.ts +++ b/src/client.ts @@ -28,9 +28,11 @@ import { OperationResult, OperationType, RequestPolicy, + PromisifiedSource, } from './types'; -import { toSuspenseSource } from './utils'; +import { createRequest, toSuspenseSource, withPromise } from './utils'; +import { DocumentNode } from 'graphql'; /** Options for configuring the URQL [client]{@link Client}. */ export interface ClientOptions { @@ -196,6 +198,16 @@ export class Client { } }; + query( + query: DocumentNode | string, + variables?: object, + context?: Partial + ): PromisifiedSource { + return withPromise( + this.executeQuery(createRequest(query, variables), context) + ); + } + executeQuery = ( query: GraphQLRequest, opts?: Partial @@ -209,9 +221,9 @@ export class Client { merge([fromValue(0), interval(pollInterval)]), switchMap(() => response$) ); - } else { - return response$; } + + return response$; }; executeSubscription = ( @@ -222,6 +234,16 @@ export class Client { return this.executeRequestOperation(operation); }; + mutation( + query: DocumentNode | string, + variables?: object, + context?: Partial + ): PromisifiedSource { + return withPromise( + this.executeMutation(createRequest(query, variables), context) + ); + } + executeMutation = ( query: GraphQLRequest, opts?: Partial diff --git a/src/types.ts b/src/types.ts index fbb320bf8f..9e16dbf26f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,10 @@ export { ExecutionResult } from 'graphql'; /** Utility type to Omit keys from an interface/object type */ export type Omit = Pick>; +export type PromisifiedSource = Source & { + toPromise: () => Promise; +}; + /** The type of GraphQL operation being executed. */ export type OperationType = 'subscription' | 'query' | 'mutation' | 'teardown'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 7d2e909d91..e17b2d560b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,6 +3,7 @@ export * from './request'; export * from './result'; export * from './typenames'; export * from './toSuspenseSource'; +export * from './withPromise'; export const noop = () => { /* noop */ diff --git a/src/utils/withPromise.ts b/src/utils/withPromise.ts new file mode 100644 index 0000000000..78fdd5f683 --- /dev/null +++ b/src/utils/withPromise.ts @@ -0,0 +1,12 @@ +import { Source, pipe, toPromise, take } from 'wonka'; +import { PromisifiedSource } from '../types'; + +export function withPromise(source$: Source): PromisifiedSource { + (source$ as PromisifiedSource).toPromise = () => + pipe( + source$, + take(1), + toPromise + ); + return source$ as PromisifiedSource; +}