Skip to content

Commit

Permalink
Thin Client Integration packages (#2066)
Browse files Browse the repository at this point in the history
* Thin Client Integration packages

* 🤦

* ..
  • Loading branch information
ardatan committed Nov 11, 2022
1 parent 643f50e commit 13ecb7f
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 334 deletions.
6 changes: 6 additions & 0 deletions .changeset/giant-spoons-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-yoga/apollo-link': patch
'@graphql-yoga/urql-exchange': patch
---

Thinner Client integration packages
6 changes: 5 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ if (process.env.INTEGRATION_TEST === 'true') {

// tests that leak due to external dependencies
if (process.env.LEAKS_TEST === 'true') {
testMatch.push('!**/hackernews.spec.ts')
testMatch.push(
'!**/hackernews.spec.ts',
'!**/urql-exchange.spec.ts',
'!**/apollo-link.spec.ts',
)
}

testMatch.push('!**/dist/**', '!**/.bob/**')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe.skip('Yoga Apollo Link', () => {
client = new ApolloClient({
link: new YogaLink({
endpoint: url,
customFetch: yoga.fetch as WindowOrWorkerGlobalScope['fetch'],
fetch: yoga.fetch as WindowOrWorkerGlobalScope['fetch'],
}),
cache: new InMemoryCache(),
})
Expand Down
4 changes: 2 additions & 2 deletions packages/client/apollo-link/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"access": "public"
},
"dependencies": {
"@graphql-tools/url-loader": "7.16.12",
"@graphql-tools/utils": "9.1.0",
"@graphql-tools/executor-http": "0.0.2",
"@graphql-tools/executor-apollo-link": "0.0.2",
"tslib": "^2.3.1"
},
"devDependencies": {
Expand Down
63 changes: 7 additions & 56 deletions packages/client/apollo-link/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,13 @@
import * as apolloImport from '@apollo/client'
import { ExecutorLink } from '@graphql-tools/executor-apollo-link'
import {
LoadFromUrlOptions,
SubscriptionProtocol,
UrlLoader,
} from '@graphql-tools/url-loader'
import { ExecutionRequest, isAsyncIterable } from '@graphql-tools/utils'
HTTPExecutorOptions,
buildHTTPExecutor,
} from '@graphql-tools/executor-http'

export type YogaLinkOptions = LoadFromUrlOptions & { endpoint: string }
export type YogaLinkOptions = HTTPExecutorOptions

const apollo: typeof apolloImport =
(apolloImport as any)?.default ?? apolloImport

function createYogaApolloRequestHandler(
options: YogaLinkOptions,
): apolloImport.RequestHandler {
const urlLoader = new UrlLoader()
const executor = urlLoader.getExecutorAsync(options.endpoint, {
subscriptionsProtocol: SubscriptionProtocol.SSE,
multipart: true,
...options,
})
return function graphQLYogaApolloRequestHandler(
operation: apolloImport.Operation,
): apolloImport.Observable<apolloImport.FetchResult> {
return new apollo.Observable((observer) => {
const executionRequest: ExecutionRequest = {
document: operation.query,
variables: operation.variables,
operationName: operation.operationName,
extensions: operation.extensions,
context: operation.getContext(),
}
executor(executionRequest)
.then(async (results) => {
if (isAsyncIterable(results)) {
for await (const result of results) {
if (observer.closed) {
return
}
observer.next(result)
}
observer.complete()
} else if (!observer.closed) {
observer.next(results)
observer.complete()
}
})
.catch((error) => {
if (!observer.closed) {
observer.error(error)
}
})
})
}
}

export class YogaLink extends apollo.ApolloLink {
export class YogaLink extends ExecutorLink {
constructor(options: YogaLinkOptions) {
super(createYogaApolloRequestHandler(options))
super(buildHTTPExecutor(options as any))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe.skip('URQL Yoga Exchange', () => {
url,
exchanges: [
yogaExchange({
customFetch: yoga.fetch as WindowOrWorkerGlobalScope['fetch'],
fetch: yoga.fetch as WindowOrWorkerGlobalScope['fetch'],
}),
],
})
Expand Down
4 changes: 2 additions & 2 deletions packages/client/urql-exchange/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"access": "public"
},
"dependencies": {
"@graphql-tools/url-loader": "7.16.12",
"@graphql-tools/utils": "9.1.0",
"@graphql-tools/executor-http": "0.0.2",
"@graphql-tools/executor-urql-exchange": "0.0.2",
"tslib": "^2.4.0"
},
"devDependencies": {
Expand Down
149 changes: 8 additions & 141 deletions packages/client/urql-exchange/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,12 @@
import {
Source,
pipe,
share,
filter,
takeUntil,
mergeMap,
merge,
make,
} from 'wonka'
buildHTTPExecutor,
HTTPExecutorOptions,
} from '@graphql-tools/executor-http'
import { executorExchange } from '@graphql-tools/executor-urql-exchange'
import { Exchange } from '@urql/core'

import {
Exchange,
ExecutionResult,
makeResult,
makeErrorResult,
mergeResultPatch,
Operation,
OperationResult,
getOperationName,
OperationContext,
ExchangeIO,
AnyVariables,
} from '@urql/core'

import { ExecutionRequest, isAsyncIterable } from '@graphql-tools/utils'
import {
LoadFromUrlOptions,
SubscriptionProtocol,
UrlLoader,
} from '@graphql-tools/url-loader'
import { OperationTypeNode } from 'graphql'

export type YogaExchangeOptions = LoadFromUrlOptions

export function yogaExchange(options?: YogaExchangeOptions): Exchange {
const urlLoader = new UrlLoader()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function makeYogaSource<TData extends Record<string, any>>(
operation: Operation<TData>,
): Source<OperationResult<TData>> {
const operationName = getOperationName(operation.query)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const executionRequest: ExecutionRequest<any, OperationContext> = {
document: operation.query,
operationName,
operationType: operation.kind as OperationTypeNode,
variables: operation.variables,
context: operation.context,
extensions: {
endpoint: operation.context.url,
headers: operation.context.headers,
},
}
const extraFetchOptions =
typeof operation.context.fetchOptions === 'function'
? operation.context.fetchOptions()
: operation.context.fetchOptions
const executor = urlLoader.getExecutorAsync(
options?.endpoint || operation.context.url,
{
subscriptionsProtocol: SubscriptionProtocol.SSE,
multipart: true,
customFetch: operation.context.fetch,
useGETForQueries: !!operation.context.preferGetMethod,
headers: extraFetchOptions?.headers as Record<string, string>,
method: extraFetchOptions?.method as 'GET' | 'POST',
credentials: extraFetchOptions?.credentials,
...options,
},
)
return make<OperationResult<TData>>((observer) => {
let ended = false
executor(executionRequest)
.then(
async (result: ExecutionResult | AsyncIterable<ExecutionResult>) => {
if (ended || !result) {
return
}
if (!isAsyncIterable(result)) {
observer.next(makeResult(operation, result))
} else {
let prevResult: OperationResult<TData, AnyVariables> | null = null

for await (const value of result) {
if (value) {
prevResult = prevResult
? mergeResultPatch(prevResult, value)
: makeResult(operation, value)
observer.next(prevResult)
}
if (ended) {
break
}
}
}
observer.complete()
},
)
.catch((error) => {
observer.next(makeErrorResult(operation, error))
})
.finally(() => {
ended = true
observer.complete()
})
return () => {
ended = true
}
})
}
return function yogaExchangeFn({ forward }): ExchangeIO {
return function yogaExchangeIO<TData, TVariables extends AnyVariables>(
ops$: Source<Operation<TData, TVariables>>,
): Source<OperationResult<TData>> {
const sharedOps$ = share(ops$)

const executedOps$ = pipe(
sharedOps$,
filter(
(operation) =>
operation.kind === 'query' ||
operation.kind === 'mutation' ||
operation.kind === 'subscription',
),
mergeMap((operation) => {
const teardown$ = pipe(
sharedOps$,
filter((op) => op.kind === 'teardown' && op.key === operation.key),
)

return pipe(makeYogaSource(operation), takeUntil(teardown$))
}),
)

const forwardedOps$ = pipe(
sharedOps$,
filter((operation) => operation.kind === 'teardown'),
forward,
)
export type YogaExchangeOptions = HTTPExecutorOptions

return merge([executedOps$, forwardedOps$])
}
}
export function yogaExchange(options?: HTTPExecutorOptions): Exchange {
return executorExchange(buildHTTPExecutor(options as any))
}
Loading

0 comments on commit 13ecb7f

Please sign in to comment.