Skip to content

Commit c0ca319

Browse files
authoredJun 29, 2021
Remove Subscriber and use only Executor (#3117)
* Remove Subscriber and use only Executor * Fix introspectSchema * Fix tests * Cleanup
1 parent eb233da commit c0ca319

File tree

17 files changed

+178
-273
lines changed

17 files changed

+178
-273
lines changed
 

‎.changeset/nine-ducks-burn.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-tools/wrap': major
3+
---
4+
5+
BREAKING CHANGE
6+
- Remove unnecessary `introspectSchemaSync`, `introspectSchema` already handles sync execution

‎.changeset/sharp-wombats-perform.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@graphql-tools/batch-execute': major
3+
'@graphql-tools/delegate': major
4+
'@graphql-tools/links': major
5+
'@graphql-tools/url-loader': major
6+
'@graphql-tools/stitch': major
7+
'@graphql-tools/utils': major
8+
'@graphql-tools/wrap': major
9+
---
10+
11+
BREAKING CHANGE
12+
- Remove Subscriber and use only Executor
13+
- - Now `Executor` can receive `AsyncIterable` and subscriptions will also be handled by `Executor`. This is a future-proof change for defer, stream and live queries
14+
15+

‎packages/batch-execute/src/createBatchingExecutor.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export function createBatchingExecutor(
1818
) => Record<string, any> = defaultExtensionsReducer
1919
): Executor {
2020
const loader = new DataLoader(createLoadFn(executor, extensionsReducer), dataLoaderOptions);
21-
return (executionParams: ExecutionParams) => loader.load(executionParams);
21+
return (executionParams: ExecutionParams) =>
22+
executionParams.info?.operation.operation === 'subscription'
23+
? executor(executionParams)
24+
: loader.load(executionParams);
2225
}
2326

2427
function createLoadFn(
@@ -53,7 +56,7 @@ function createLoadFn(
5356

5457
const executionResults: Array<ValueOrPromise<ExecutionResult>> = execBatches.map(execBatch => {
5558
const mergedExecutionParams = mergeExecutionParams(execBatch, extensionsReducer);
56-
return new ValueOrPromise(() => executor(mergedExecutionParams));
59+
return new ValueOrPromise(() => executor(mergedExecutionParams) as ExecutionResult);
5760
});
5861

5962
return ValueOrPromise.all(executionResults)

‎packages/delegate/src/Subschema.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GraphQLSchema } from 'graphql';
33
import { SubschemaConfig, Transform, MergedTypeConfig, CreateProxyingResolverFn, BatchingOptions } from './types';
44

55
import { applySchemaTransforms } from './applySchemaTransforms';
6-
import { Executor, Subscriber } from '@graphql-tools/utils';
6+
import { Executor } from '@graphql-tools/utils';
77

88
export function isSubschema(value: any): value is Subschema {
99
return Boolean(value.transformedSchema);
@@ -20,7 +20,6 @@ export class Subschema<K = any, V = any, C = K, TContext = Record<string, any>>
2020
public schema: GraphQLSchema;
2121

2222
public executor?: Executor<TContext>;
23-
public subscriber?: Subscriber<TContext>;
2423
public batch?: boolean;
2524
public batchingOptions?: BatchingOptions<K, V, C>;
2625

@@ -34,7 +33,6 @@ export class Subschema<K = any, V = any, C = K, TContext = Record<string, any>>
3433
this.schema = config.schema;
3534

3635
this.executor = config.executor;
37-
this.subscriber = config.subscriber;
3836
this.batch = config.batch;
3937
this.batchingOptions = config.batchingOptions;
4038

‎packages/delegate/src/delegateToSchema.ts

+31-58
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ import { getBatchingExecutor } from '@graphql-tools/batch-execute';
1818

1919
import {
2020
mapAsyncIterator,
21-
ExecutionResult,
2221
Executor,
2322
ExecutionParams,
24-
Subscriber,
2523
Maybe,
2624
assertSome,
2725
AggregateError,
26+
isAsyncIterable,
2827
} from '@graphql-tools/utils';
2928

3029
import {
@@ -102,41 +101,27 @@ export function delegateRequest<TContext = Record<string, any>, TArgs = any>(
102101
validateRequest(delegationContext, processedRequest.document);
103102
}
104103

105-
const { operation, context, info } = delegationContext;
104+
const { context, info } = delegationContext;
106105

107-
if (operation === 'query' || operation === 'mutation') {
108-
const executor = getExecutor(delegationContext);
106+
const executor = getExecutor(delegationContext);
109107

110-
return new ValueOrPromise(() =>
111-
executor({
112-
...processedRequest,
113-
context,
114-
info,
115-
})
116-
)
117-
.then(originalResult => transformer.transformResult(originalResult))
118-
.resolve();
119-
}
120-
121-
const subscriber = getSubscriber(delegationContext);
122-
123-
return subscriber({
124-
...processedRequest,
125-
context,
126-
info,
127-
}).then(subscriptionResult => {
128-
if (Symbol.asyncIterator in subscriptionResult) {
129-
// "subscribe" to the subscription result and map the result through the transforms
130-
return mapAsyncIterator<ExecutionResult, any>(
131-
subscriptionResult as AsyncIterableIterator<ExecutionResult>,
132-
originalResult => ({
108+
return new ValueOrPromise(() =>
109+
executor({
110+
...processedRequest,
111+
context,
112+
info,
113+
})
114+
)
115+
.then(originalResult => {
116+
if (isAsyncIterable(originalResult)) {
117+
// "subscribe" to the subscription result and map the result through the transforms
118+
return mapAsyncIterator(originalResult, originalResult => ({
133119
[delegationContext.fieldName]: transformer.transformResult(originalResult),
134-
})
135-
);
136-
}
137-
138-
return transformer.transformResult(subscriptionResult as ExecutionResult);
139-
});
120+
}));
121+
}
122+
return transformer.transformResult(originalResult);
123+
})
124+
.resolve();
140125
}
141126

142127
const emptyObject = {};
@@ -250,14 +235,14 @@ function validateRequest(delegationContext: DelegationContext<any>, document: Do
250235
}
251236

252237
function getExecutor<TContext>(delegationContext: DelegationContext<TContext>): Executor<TContext> {
253-
const { subschemaConfig, targetSchema, context } = delegationContext;
238+
const { subschemaConfig, targetSchema, context, operation } = delegationContext;
254239

255-
let executor: Executor = subschemaConfig?.executor || createDefaultExecutor(targetSchema);
240+
let executor: Executor = subschemaConfig?.executor || createDefaultExecutor(targetSchema, operation);
256241

257242
if (subschemaConfig?.batch) {
258243
const batchingOptions = subschemaConfig?.batchingOptions;
259244
executor = getBatchingExecutor(
260-
context ?? self ?? window ?? global,
245+
context ?? globalThis ?? window ?? global,
261246
executor,
262247
batchingOptions?.dataLoaderOptions,
263248
batchingOptions?.extensionsReducer
@@ -267,29 +252,17 @@ function getExecutor<TContext>(delegationContext: DelegationContext<TContext>):
267252
return executor;
268253
}
269254

270-
function getSubscriber<TContext>(delegationContext: DelegationContext<TContext>): Subscriber<TContext> {
271-
const { subschemaConfig, targetSchema } = delegationContext;
272-
return subschemaConfig?.subscriber || createDefaultSubscriber(targetSchema);
273-
}
274-
275-
function createDefaultExecutor(schema: GraphQLSchema): Executor {
276-
return (({ document, context, variables, rootValue }: ExecutionParams) =>
277-
execute({
255+
const createDefaultExecutor = (schema: GraphQLSchema, operation: OperationTypeNode) =>
256+
(({ document, context, variables, rootValue }: ExecutionParams) => {
257+
const executionParams = {
278258
schema,
279259
document,
280260
contextValue: context,
281261
variableValues: variables,
282262
rootValue,
283-
})) as Executor;
284-
}
285-
286-
function createDefaultSubscriber(schema: GraphQLSchema) {
287-
return ({ document, context, variables, rootValue }: ExecutionParams) =>
288-
subscribe({
289-
schema,
290-
document,
291-
contextValue: context,
292-
variableValues: variables,
293-
rootValue,
294-
}) as any;
295-
}
263+
};
264+
if (operation === 'subscription') {
265+
return subscribe(executionParams);
266+
}
267+
return execute(executionParams);
268+
}) as Executor;

‎packages/delegate/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ export * from './resolveExternalValue';
99
export * from './subschemaConfig';
1010
export * from './transforms';
1111
export * from './types';
12-
export { Executor, Subscriber, AsyncExecutor, SyncExecutor, ExecutionParams } from '@graphql-tools/utils';
12+
export { Executor, AsyncExecutor, SyncExecutor, ExecutionParams } from '@graphql-tools/utils';

‎packages/delegate/src/types.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414

1515
import DataLoader from 'dataloader';
1616

17-
import { ExecutionParams, ExecutionResult, Executor, Request, Subscriber, TypeMap } from '@graphql-tools/utils';
17+
import { ExecutionParams, ExecutionResult, Executor, Request, TypeMap } from '@graphql-tools/utils';
1818

1919
import { Subschema } from './Subschema';
2020
import { OBJECT_SUBSCHEMA_SYMBOL, FIELD_SUBSCHEMA_MAP_SYMBOL, UNPATHED_ERRORS_SYMBOL } from './symbols';
@@ -145,7 +145,6 @@ export interface SubschemaConfig<K = any, V = any, C = K, TContext = Record<stri
145145
transforms?: Array<Transform<any, TContext>>;
146146
merge?: Record<string, MergedTypeConfig<any, any, TContext>>;
147147
executor?: Executor<TContext>;
148-
subscriber?: Subscriber<TContext>;
149148
batch?: boolean;
150149
batchingOptions?: BatchingOptions<K, V, C>;
151150
}

‎packages/links/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export { createServerHttpLink } from './createServerHttpLink';
22
export { AwaitVariablesLink } from './AwaitVariablesLink';
33
export { linkToExecutor } from './linkToExecutor';
4-
export { linkToSubscriber } from './linkToSubscriber';
54
export { GraphQLUpload } from './GraphQLUpload';

‎packages/links/src/linkToExecutor.ts

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
1+
import { toPromise } from '@apollo/client/core';
12
import { ApolloLink, execute } from '@apollo/client/link/core';
23
import { Observable } from '@apollo/client/utilities';
3-
import { toPromise } from '@apollo/client/link/utils';
44

5-
import { AsyncExecutor, ExecutionParams, ExecutionResult } from '@graphql-tools/utils';
5+
import { Executor, ExecutionParams, ExecutionResult, observableToAsyncIterable } from '@graphql-tools/utils';
66

77
export const linkToExecutor =
8-
(link: ApolloLink): AsyncExecutor =>
9-
<TReturn, TArgs, TContext>(params: ExecutionParams<TArgs, TContext>): Promise<ExecutionResult<TReturn>> => {
8+
(link: ApolloLink): Executor =>
9+
async <TReturn, TArgs, TContext>(params: ExecutionParams<TArgs, TContext>) => {
1010
const { document, variables, extensions, context, info, operationName } = params;
11-
return toPromise(
12-
execute(link, {
13-
query: document,
14-
variables: variables,
15-
context: {
16-
graphqlContext: context,
17-
graphqlResolveInfo: info,
18-
clientAwareness: {},
19-
},
20-
extensions,
21-
operationName,
22-
}) as Observable<ExecutionResult<TReturn>>
23-
);
11+
const observable = execute(link, {
12+
query: document,
13+
variables,
14+
context: {
15+
graphqlContext: context,
16+
graphqlResolveInfo: info,
17+
clientAwareness: {},
18+
},
19+
extensions,
20+
operationName,
21+
}) as Observable<ExecutionResult<TReturn>>;
22+
if (info?.operation.operation === 'subscription') {
23+
return observableToAsyncIterable<ExecutionResult<TReturn>>(observable)[Symbol.asyncIterator]();
24+
}
25+
return toPromise(observable);
2426
};

‎packages/links/src/linkToSubscriber.ts

-25
This file was deleted.

‎packages/loaders/url/src/index.ts

+26-52
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { print, IntrospectionOptions, Kind, GraphQLError } from 'graphql';
55
import {
66
AsyncExecutor,
77
Executor,
8-
Subscriber,
98
SyncExecutor,
109
SchemaPointerSingle,
1110
Source,
@@ -382,11 +381,11 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
382381
return executor;
383382
}
384383

385-
buildWSSubscriber(
384+
buildWSExecutor(
386385
subscriptionsEndpoint: string,
387386
webSocketImpl: typeof WebSocket,
388387
connectionParams?: ClientOptions['connectionParams']
389-
): Subscriber {
388+
): AsyncExecutor {
390389
const WS_URL = switchProtocols(subscriptionsEndpoint, {
391390
https: 'wss',
392391
http: 'ws',
@@ -418,11 +417,11 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
418417
};
419418
}
420419

421-
buildWSLegacySubscriber(
420+
buildWSLegacyExecutor(
422421
subscriptionsEndpoint: string,
423422
webSocketImpl: typeof WebSocket,
424423
connectionParams?: ConnectionParamsOptions
425-
): Subscriber {
424+
): AsyncExecutor {
426425
const WS_URL = switchProtocols(subscriptionsEndpoint, {
427426
https: 'wss',
428427
http: 'ws',
@@ -447,12 +446,12 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
447446
};
448447
}
449448

450-
buildSSESubscriber(
449+
buildSSEExecutor(
451450
pointer: string,
452451
extraHeaders: HeadersConfig | undefined,
453452
fetch: AsyncFetchFn,
454453
options: Maybe<FetchEventSourceInit>
455-
): Subscriber<any, ExecutionExtensions> {
454+
): AsyncExecutor<any, ExecutionExtensions> {
456455
return async ({ document, variables, extensions }) => {
457456
const controller = new AbortController();
458457
const query = print(document);
@@ -558,14 +557,11 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
558557
}
559558
}
560559

561-
async getExecutorAndSubscriberAsync(
562-
pointer: SchemaPointerSingle,
563-
options: LoadFromUrlOptions = {}
564-
): Promise<{ executor: AsyncExecutor; subscriber: Subscriber }> {
560+
async getExecutorAsync(pointer: SchemaPointerSingle, options: LoadFromUrlOptions = {}): Promise<AsyncExecutor> {
565561
const fetch = await this.getFetch(options.customFetch, asyncImport, true);
566562
const defaultMethod = this.getDefaultMethodFromOptions(options.method, 'POST');
567563

568-
const executor = this.buildExecutor({
564+
const httpExecutor = this.buildExecutor({
569565
pointer,
570566
fetch,
571567
extraHeaders: options.headers,
@@ -574,31 +570,35 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
574570
multipart: options.multipart,
575571
});
576572

577-
let subscriber: Subscriber;
573+
let subscriptionExecutor: AsyncExecutor;
578574

579575
const subscriptionsEndpoint = options.subscriptionsEndpoint || pointer;
580576
if (options.useSSEForSubscription) {
581-
subscriber = this.buildSSESubscriber(subscriptionsEndpoint, options.headers, fetch, options.eventSourceOptions);
577+
subscriptionExecutor = this.buildSSEExecutor(
578+
subscriptionsEndpoint,
579+
options.headers,
580+
fetch,
581+
options.eventSourceOptions
582+
);
582583
} else {
583584
const webSocketImpl = await this.getWebSocketImpl(options, asyncImport);
584585
const connectionParams = () => ({ headers: options.headers });
585586
if (options.useWebSocketLegacyProtocol) {
586-
subscriber = this.buildWSLegacySubscriber(subscriptionsEndpoint, webSocketImpl, connectionParams);
587+
subscriptionExecutor = this.buildWSLegacyExecutor(subscriptionsEndpoint, webSocketImpl, connectionParams);
587588
} else {
588-
subscriber = this.buildWSSubscriber(subscriptionsEndpoint, webSocketImpl, connectionParams);
589+
subscriptionExecutor = this.buildWSExecutor(subscriptionsEndpoint, webSocketImpl, connectionParams);
589590
}
590591
}
591592

592-
return {
593-
executor,
594-
subscriber,
593+
return params => {
594+
if (params.info?.operation.operation === 'subscription') {
595+
return subscriptionExecutor(params);
596+
}
597+
return httpExecutor(params);
595598
};
596599
}
597600

598-
getExecutorAndSubscriberSync(
599-
pointer: SchemaPointerSingle,
600-
options: LoadFromUrlOptions
601-
): { executor: SyncExecutor; subscriber: Subscriber } {
601+
getExecutorSync(pointer: SchemaPointerSingle, options: LoadFromUrlOptions): SyncExecutor {
602602
const fetch = this.getFetch(options?.customFetch, syncImport, false);
603603
const defaultMethod = this.getDefaultMethodFromOptions(options?.method, 'POST');
604604

@@ -610,48 +610,22 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
610610
useGETForQueries: options.useGETForQueries,
611611
});
612612

613-
const subscriptionsEndpoint = options.subscriptionsEndpoint || pointer;
614-
let subscriber: Subscriber;
615-
if (options.useSSEForSubscription) {
616-
const asyncFetchFn: any = (...args: any[]) =>
617-
this.getFetch(options?.customFetch, asyncImport, true).then((asyncFetch: any) => asyncFetch(...args));
618-
subscriber = this.buildSSESubscriber(
619-
subscriptionsEndpoint,
620-
options.headers,
621-
asyncFetchFn,
622-
options.eventSourceOptions
623-
);
624-
} else {
625-
const webSocketImpl = this.getWebSocketImpl(options, syncImport);
626-
const connectionParams = () => ({ headers: options.headers });
627-
if (options.useWebSocketLegacyProtocol) {
628-
subscriber = this.buildWSLegacySubscriber(subscriptionsEndpoint, webSocketImpl, connectionParams);
629-
} else {
630-
subscriber = this.buildWSSubscriber(subscriptionsEndpoint, webSocketImpl, connectionParams);
631-
}
632-
}
633-
634-
return {
635-
executor,
636-
subscriber,
637-
};
613+
return executor;
638614
}
639615

640616
async getSubschemaConfigAsync(pointer: SchemaPointerSingle, options: LoadFromUrlOptions): Promise<SubschemaConfig> {
641-
const { executor, subscriber } = await this.getExecutorAndSubscriberAsync(pointer, options);
617+
const executor = await this.getExecutorAsync(pointer, options);
642618
return {
643619
schema: await introspectSchema(executor, undefined, options as IntrospectionOptions),
644620
executor,
645-
subscriber,
646621
};
647622
}
648623

649624
getSubschemaConfigSync(pointer: SchemaPointerSingle, options: LoadFromUrlOptions): SubschemaConfig {
650-
const { executor, subscriber } = this.getExecutorAndSubscriberSync(pointer, options);
625+
const executor = this.getExecutorSync(pointer, options);
651626
return {
652627
schema: introspectSchema(executor, undefined, options as IntrospectionOptions),
653628
executor,
654-
subscriber,
655629
};
656630
}
657631

‎packages/stitch/tests/fixtures/schemas.ts

+17-22
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,23 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({
682682
});
683683

684684
function makeExecutorFromSchema(schema: GraphQLSchema) {
685-
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
685+
return async <TReturn, TArgs, TContext>({ document, variables, context, info }: ExecutionParams<TArgs, TContext>) => {
686+
if (info?.operation.operation === 'subscription') {
687+
const result = subscribe(
688+
schema,
689+
document,
690+
null,
691+
context,
692+
variables,
693+
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;
694+
if (isPromise(result)) {
695+
return result.then(asyncIterator => {
696+
assertAsyncIterable(asyncIterator)
697+
return mapAsyncIterator<any, any>(asyncIterator, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult)))
698+
});
699+
}
700+
return JSON.parse(JSON.stringify(result));
701+
}
686702
return (new ValueOrPromise(() => graphql(
687703
schema,
688704
print(document),
@@ -701,35 +717,14 @@ function assertAsyncIterable(input: unknown): asserts input is AsyncIterableIter
701717
}
702718
}
703719

704-
function makeSubscriberFromSchema(schema: GraphQLSchema) {
705-
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
706-
const result = subscribe(
707-
schema,
708-
document,
709-
null,
710-
context,
711-
variables,
712-
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;
713-
if (isPromise(result)) {
714-
return result.then(asyncIterator => {
715-
assertAsyncIterable(asyncIterator)
716-
return mapAsyncIterator<any, any>(asyncIterator, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult)))
717-
});
718-
}
719-
return JSON.parse(JSON.stringify(result));
720-
};
721-
}
722-
723720
export async function makeSchemaRemote(
724721
schema: GraphQLSchema,
725722
): Promise<SubschemaConfig> {
726723
const executor = makeExecutorFromSchema(schema);
727-
const subscriber = makeSubscriberFromSchema(schema);
728724
const clientSchema = await introspectSchema(executor);
729725
return {
730726
schema: clientSchema,
731727
executor,
732-
subscriber,
733728
batch: true,
734729
};
735730
}

‎packages/utils/src/executor.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { DocumentNode, GraphQLResolveInfo } from 'graphql';
22
import { ExecutionResult } from './Interfaces';
33

4+
type MaybePromise<T> = Promise<T> | T;
5+
type MaybeAsyncIterableIterator<T> = AsyncIterableIterator<T> | T;
6+
47
export interface ExecutionParams<
58
TArgs extends Record<string, any> = Record<string, any>,
69
TContext = any,
@@ -24,7 +27,8 @@ export type AsyncExecutor<TBaseContext = Record<string, any>, TBaseExtensions =
2427
TExtensions extends TBaseExtensions = TBaseExtensions
2528
>(
2629
params: ExecutionParams<TArgs, TContext, TRoot, TExtensions>
27-
) => Promise<ExecutionResult<TReturn>>;
30+
) => Promise<MaybeAsyncIterableIterator<ExecutionResult<TReturn>>>;
31+
2832
export type SyncExecutor<TBaseContext = Record<string, any>, TBaseExtensions = Record<string, any>> = <
2933
TReturn = any,
3034
TArgs = Record<string, any>,
@@ -34,6 +38,7 @@ export type SyncExecutor<TBaseContext = Record<string, any>, TBaseExtensions = R
3438
>(
3539
params: ExecutionParams<TArgs, TContext, TRoot, TExtensions>
3640
) => ExecutionResult<TReturn>;
41+
3742
export type Executor<TBaseContext = Record<string, any>, TBaseExtensions = Record<string, any>> = <
3843
TReturn = any,
3944
TArgs = Record<string, any>,
@@ -42,13 +47,4 @@ export type Executor<TBaseContext = Record<string, any>, TBaseExtensions = Recor
4247
TExtensions extends TBaseExtensions = TBaseExtensions
4348
>(
4449
params: ExecutionParams<TArgs, TContext, TRoot, TExtensions>
45-
) => ExecutionResult<TReturn> | Promise<ExecutionResult<TReturn>>;
46-
export type Subscriber<TBaseContext = Record<string, any>, TBaseExtensions = Record<string, any>> = <
47-
TReturn = any,
48-
TArgs = Record<string, any>,
49-
TContext extends TBaseContext = TBaseContext,
50-
TRoot = any,
51-
TExtensions extends TBaseExtensions = TBaseExtensions
52-
>(
53-
params: ExecutionParams<TArgs, TContext, TRoot, TExtensions>
54-
) => Promise<AsyncIterableIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;
50+
) => MaybePromise<MaybeAsyncIterableIterator<ExecutionResult<TReturn>>>;

‎packages/wrap/src/introspect.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010

1111
import { ValueOrPromise } from 'value-or-promise';
1212

13-
import { AsyncExecutor, Executor, SyncExecutor, ExecutionResult, AggregateError } from '@graphql-tools/utils';
13+
import { AsyncExecutor, Executor, ExecutionResult, AggregateError, isAsyncIterable } from '@graphql-tools/utils';
1414

1515
function getSchemaFromIntrospection(introspectionResult: ExecutionResult<IntrospectionQuery>): GraphQLSchema {
1616
if (introspectionResult?.data?.__schema) {
@@ -27,7 +27,7 @@ function getSchemaFromIntrospection(introspectionResult: ExecutionResult<Introsp
2727
}
2828
}
2929

30-
export function introspectSchema<TExecutor extends AsyncExecutor | SyncExecutor>(
30+
export function introspectSchema<TExecutor extends Executor>(
3131
executor: TExecutor,
3232
context?: Record<string, any>,
3333
options?: IntrospectionOptions
@@ -39,15 +39,12 @@ export function introspectSchema<TExecutor extends AsyncExecutor | SyncExecutor>
3939
context,
4040
})
4141
)
42+
.then(introspection => {
43+
if (isAsyncIterable(introspection)) {
44+
return introspection.next().then(({ value }) => value);
45+
}
46+
return introspection;
47+
})
4248
.then(introspection => getSchemaFromIntrospection(introspection))
4349
.resolve() as any;
4450
}
45-
46-
// Keep for backwards compatibility. Will be removed on next release.
47-
export function introspectSchemaSync(
48-
executor: SyncExecutor,
49-
context?: Record<string, any>,
50-
options?: IntrospectionOptions
51-
) {
52-
return introspectSchema(executor, context, options);
53-
}

‎packages/wrap/tests/fixtures/schemas.ts

+32-38
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,7 @@ const propertyRootTypeDefs = `
284284
foo: String
285285
}
286286
287-
${
288-
'getInterfaces' in GraphQLInterfaceType.prototype
287+
${'getInterfaces' in GraphQLInterfaceType.prototype
289288
? `interface TestNestedInterface implements TestInterface {
290289
kind: TestInterfaceKind
291290
testString: String
@@ -296,7 +295,7 @@ const propertyRootTypeDefs = `
296295
testString: String
297296
bar: String
298297
}`
299-
: `type TestImpl2 implements TestInterface {
298+
: `type TestImpl2 implements TestInterface {
300299
kind: TestInterfaceKind
301300
testString: String
302301
bar: String
@@ -363,27 +362,27 @@ const propertyResolvers: IResolvers = {
363362
interfaceTest(_root, { kind }) {
364363
return kind === 'ONE'
365364
? {
366-
kind: 'ONE',
367-
testString: 'test',
368-
foo: 'foo',
369-
}
365+
kind: 'ONE',
366+
testString: 'test',
367+
foo: 'foo',
368+
}
370369
: {
371-
kind: 'TWO',
372-
testString: 'test',
373-
bar: 'bar',
374-
};
370+
kind: 'TWO',
371+
testString: 'test',
372+
bar: 'bar',
373+
};
375374
},
376375

377376
unionTest(_root, { output }) {
378377
return output === 'Interface'
379378
? {
380-
kind: 'ONE',
381-
testString: 'test',
382-
foo: 'foo',
383-
}
379+
kind: 'ONE',
380+
testString: 'test',
381+
foo: 'foo',
382+
}
384383
: {
385-
someField: 'Bar',
386-
};
384+
someField: 'Bar',
385+
};
387386
},
388387

389388
errorTest() {
@@ -679,45 +678,40 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({
679678
});
680679

681680
function makeExecutorFromSchema(schema: GraphQLSchema) {
682-
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
681+
return async <TReturn, TArgs, TContext>({ document, variables, context, info }: ExecutionParams<TArgs, TContext>) => {
682+
if (info?.operation.operation === 'subscription') {
683+
const result = await subscribe(
684+
schema,
685+
document,
686+
null,
687+
context,
688+
variables,
689+
);
690+
if (isAsyncIterable<ExecutionResult<TReturn>>(result)) {
691+
return mapAsyncIterator<ExecutionResult<TReturn>, TReturn>(result, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult)));
692+
}
693+
return JSON.parse(JSON.stringify(result));
694+
}
683695
return (new ValueOrPromise(() => graphql(
684696
schema,
685697
print(document),
686698
null,
687699
context,
688700
variables,
689701
))
690-
.then(originalResult => JSON.parse(JSON.stringify(originalResult)))
691-
.resolve()) as Promise<ExecutionResult<TReturn>> | ExecutionResult<TReturn>;
692-
};
693-
}
694-
695-
function makeSubscriberFromSchema(schema: GraphQLSchema) {
696-
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
697-
const result = await subscribe(
698-
schema,
699-
document,
700-
null,
701-
context,
702-
variables,
703-
);
704-
if (isAsyncIterable<ExecutionResult<TReturn>>(result)) {
705-
return mapAsyncIterator<ExecutionResult<TReturn>, TReturn>(result, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult)));
706-
}
707-
return JSON.parse(JSON.stringify(result));
702+
.then(originalResult => JSON.parse(JSON.stringify(originalResult)))
703+
.resolve()) as Promise<ExecutionResult<TReturn>> | ExecutionResult<TReturn>;
708704
};
709705
}
710706

711707
export async function makeSchemaRemote(
712708
schema: GraphQLSchema,
713709
): Promise<SubschemaConfig> {
714710
const executor = makeExecutorFromSchema(schema);
715-
const subscriber = makeSubscriberFromSchema(schema);
716711
const clientSchema = await introspectSchema(executor);
717712
return {
718713
schema: clientSchema,
719714
executor,
720-
subscriber,
721715
};
722716
}
723717

‎website/docs/remote-schemas.md

+7-26
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,16 @@ There two ways to create remote schemas;
1010

1111
## Use Loaders to load schemas easily
1212

13-
Check out [Schema Loading](/docs/schema-loading) to load schemas from an URL and/or different sources easily without implementing an executor or subscriber.
13+
Check out [Schema Loading](/docs/schema-loading) to load schemas from an URL and/or different sources easily without implementing an executor.
1414

15-
## Create a remote executable schema with custom executor and subscriber methods
15+
## Create a remote executable schema with custom executor methods
1616

1717
Generally, to create a remote schema, you generally need just three steps:
1818

1919
1. Create a [executor](#creating-an-executor) that can retrieve results from that schema
2020
2. Use [`introspectSchema`](#introspectschemaexecutor-context) to get the non-executable schema of the remote server
2121
3. Use [`wrapSchema`](#wrapschemaschemaconfig) to create a schema that uses the executor to delegate requests to the underlying service
2222

23-
You can optionally also include a [subscriber](#creating-a-subscriber) that can retrieve real time subscription results from the remote schema (only if you are using GraphQL Subscriptions)
24-
2523
### Creating an executor
2624

2725
You can use an executor with an HTTP Client implementation (like cross-fetch). An executor is a function capable of retrieving GraphQL results. It is the same way that a GraphQL Client handles fetching data and is used by several `graphql-tools` features to do introspection or fetch results during execution.
@@ -99,21 +97,17 @@ export default async () => {
9997
};
10098
```
10199

102-
### Creating a subscriber
103-
104-
For subscriptions, we need to define a subscriber that returns `AsyncIterator`. Other than that, it has the similar API with `executor`.
100+
### Extending the executor for subscriptions
105101

106-
```ts
107-
type Subscriber = (executionParams: ExecutionParams) => Promise<AsyncIterator<ExecutionResult>>;
108-
```
102+
For subscriptions, we need to extend our executor by returning `AsyncIterator`.
109103

110104
#### Using `graphql-ws`
111105

112106
For the following example to work, the server must implement the [library's transport protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). Learn more about [`graphql-ws`](https://github.com/enisdenjo/graphql-ws).
113107

114108
```ts
115109
import { wrapSchema, introspectSchema } from '@graphql-tools/wrap';
116-
import { Executor, Subscriber } from '@graphql-tools/delegate';
110+
import { Executor } from '@graphql-tools/delegate';
117111
import { fetch } from 'cross-fetch';
118112
import { print } from 'graphql';
119113
import { observableToAsyncIterable } from '@graphql-tools/utils';
@@ -122,23 +116,11 @@ import { createClient } from 'graphql-ws';
122116
const HTTP_GRAPHQL_ENDPOINT = 'http://localhost:3000/graphql';
123117
const WS_GRAPHQL_ENDPOINT = 'ws://localhost:3000/graphql';
124118

125-
const executor: Executor = async ({ document, variables }) => {
126-
const query = print(document);
127-
const fetchResult = await fetch(HTTP_GRAPHQL_ENDPOINT, {
128-
method: 'POST',
129-
headers: {
130-
'Content-Type': 'application/json',
131-
},
132-
body: JSON.stringify({ query, variables }),
133-
});
134-
return fetchResult.json();
135-
};
136-
137119
const subscriptionClient = createClient({
138120
url: WS_GRAPHQL_ENDPOINT,
139121
});
140122

141-
const subscriber: Subscriber = ({ document, variables }) =>
123+
const executor: Executor = ({ document, variables }) =>
142124
observableToAsyncIterable({
143125
subscribe: observer => ({
144126
unsubscribe: subscriptionClient.subscribe(
@@ -169,7 +151,6 @@ export default async () => {
169151
const schema = wrapSchema({
170152
schema: await introspectSchema(executor),
171153
executor,
172-
subscriber,
173154
});
174155

175156
return schema;
@@ -260,4 +241,4 @@ const schema = wrapSchema({
260241
});
261242
```
262243

263-
Note that within the `defaultCreateProxyingResolver` function, `delegateToSchema` receives `executor` and `subscriber` functions stored on the subschema config object originally passed to `wrapSchema`. As above, use of the the `createProxyingResolver` option is helpful when you want to customize additional functionality at resolver creation time. If you just want to customize how things are proxied at the time that they are proxied, you can make do just with custom executors and subscribers.
244+
Note that within the `defaultCreateProxyingResolver` function, `delegateToSchema` receives `executor` function stored on the subschema config object originally passed to `wrapSchema`. As above, use of the the `createProxyingResolver` option is helpful when you want to customize additional functionality at resolver creation time. If you just want to customize how things are proxied at the time that they are proxied, you can make do just with custom executors.

‎website/docs/stitch-combining-schemas.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export interface SubschemaConfig {
8888
schema: GraphQLSchema;
8989
rootValue?: Record<string, any>;
9090
executor?: Executor;
91-
subscriber?: Subscriber;
9291
createProxyingResolver?: CreateProxyingResolverFn;
9392
transforms?: Array<Transform>;
9493
merge?: Record<string, MergedTypeConfig>;
@@ -109,7 +108,7 @@ Also note that these subschema config objects may need to be referenced again in
109108

110109
## Stitching remote schemas
111110

112-
To include a remote schema in the combined gateway, you must provide at least the `schema` and `executor` subschema config options, and an optional `subscriber` for subscriptions:
111+
To include a remote schema in the combined gateway, you must provide at least the `schema` and `executor` subschema config options.
113112

114113
```js
115114
import { introspectSchema } from '@graphql-tools/wrap';
@@ -129,13 +128,12 @@ async function remoteExecutor({ document, variables }) {
129128
export const postsSubschema = {
130129
schema: await introspectSchema(remoteExecutor),
131130
executor: remoteExecutor,
132-
// subscriber: remoteSubscriber
133131
};
134132
```
135133

136134
- `schema`: this is a non-executable schema representing the remote API. The remote schema may be obtained using [introspection](/docs/remote-schemas/#introspectschemaexecutor-context), or fetched as a flat SDL string (from a server or repo) and built into a schema using [`buildSchema`](https://graphql.org/graphql-js/utilities/#buildschema). Note that not all GraphQL servers enable introspection, and those that do will not include custom directives.
137135
- `executor`: is a generic method that performs requests to a remote schema. It's quite simple to [write your own](/docs/remote-schemas#creating-an-executor). Subschema config uses the executor for query and mutation operations. See [handbook example](https://github.com/gmac/schema-stitching-handbook/tree/master/combining-local-and-remote-schemas).
138-
- `subscriber`: to enable subscription operations, include a [subscriber function](/docs/remote-schemas#creating-a-subscriber) that returns an AsyncIterator. See [handbook example](https://github.com/gmac/schema-stitching-handbook/tree/master/mutations-and-subscriptions).
136+
139137

140138
## Duplicate types
141139

0 commit comments

Comments
 (0)
Please sign in to comment.