Skip to content

Commit

Permalink
Urql Exchange & Apollo Client Link (#1448)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jul 27, 2022
1 parent f46addd commit 87d1070
Show file tree
Hide file tree
Showing 12 changed files with 716 additions and 129 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-knives-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-yoga/urql-exchange': major
---

New URQL Exchange
5 changes: 5 additions & 0 deletions .changeset/pretty-cycles-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-yoga/apollo-link': major
---

New Apollo Link
4 changes: 1 addition & 3 deletions examples/sveltekit/__tests__/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ let toSkip = false;

describe('SvelteKit integration', () => {
beforeAll(async () => {
// console.time('Setup SvelteKit tests');

const nodeVersion = execSync('node -v').toString();
if (nodeVersion.includes('v12')) {
Expand All @@ -28,7 +27,7 @@ describe('SvelteKit integration', () => {
// Kill the port if it's used!
try {
execSync('fuser -k 3007/tcp');
} catch (error) {}
} catch (error) { }

// Build svelteKit
execSync('yarn workspace sveltekit build');
Expand All @@ -49,7 +48,6 @@ describe('SvelteKit integration', () => {
}

// How long it took?
// console.timeEnd('Setup SvelteKit tests');
}, timings.setup.total);

beforeEach(async () => {
Expand Down
64 changes: 64 additions & 0 deletions packages/apollo-link/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "@graphql-yoga/apollo-link",
"version": "0.0.0",
"description": "",
"repository": {
"type": "git",
"url": "https://github.com/dotansimha/graphql-yoga.git",
"directory": "packages/apollo-link"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"scripts": {
"check": "tsc --pretty --noEmit"
},
"keywords": [
"graphql",
"server",
"graphql-yoga",
"apollo"
],
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
"license": "MIT",
"buildOptions": {
"input": "./src/index.ts"
},
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"typescript": {
"definition": "dist/typings/index.d.ts"
},
"publishConfig": {
"directory": "dist",
"access": "public"
},
"dependencies": {
"@graphql-tools/url-loader": "7.12.4",
"@graphql-tools/utils": "8.8.0",
"tslib": "^2.3.1"
},
"devDependencies": {
"@apollo/client": "3.6.9"
},
"peerDependencies": {
"graphql": "^15.2.0 || ^16.0.0",
"@apollo/client": "^3.5.9"
},
"type": "module"
}
64 changes: 64 additions & 0 deletions packages/apollo-link/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as apolloImport from '@apollo/client'
import {
LoadFromUrlOptions,
SubscriptionProtocol,
UrlLoader,
} from '@graphql-tools/url-loader'
import { ExecutionRequest, isAsyncIterable } from '@graphql-tools/utils'

export type YogaLinkOptions = LoadFromUrlOptions & { endpoint: string }

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 {
constructor(options: YogaLinkOptions) {
super(createYogaApolloRequestHandler(options))
}
}
119 changes: 119 additions & 0 deletions packages/apollo-link/test/apollo-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ApolloClient, FetchResult, InMemoryCache } from '@apollo/client/core'
import { createYoga } from 'graphql-yoga'
import { createServer } from 'http'
import { parse } from 'graphql'
import { observableToAsyncIterable } from '@graphql-tools/utils'
import { YogaLink } from '../src'
import { File } from '@whatwg-node/fetch'

describe('Yoga Apollo Link', () => {
const port = 4000 + Math.floor(Math.random() * 1000)
const endpoint = '/graphql'
const hostname = '127.0.0.1'
const yoga = createYoga({
endpoint,
logging: false,
maskedErrors: false,
schema: {
typeDefs: /* GraphQL */ `
scalar File
type Query {
hello: String
}
type Mutation {
readFile(file: File!): String!
}
type Subscription {
time: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello Apollo Client!',
},
Mutation: {
readFile: (_, args: { file: File }) => args.file.text(),
},
Subscription: {
time: {
async *subscribe() {
while (true) {
await new Promise((resolve) => setTimeout(resolve, 1000))
yield new Date().toISOString()
}
},
resolve: (str) => str,
},
},
},
},
})
const server = createServer(yoga)
const url = `http://${hostname}:${port}${endpoint}`
const client = new ApolloClient({
link: new YogaLink({
endpoint: url,
}),
cache: new InMemoryCache(),
})
beforeAll(async () => {
await new Promise<void>((resolve) => server.listen(port, hostname, resolve))
})
afterAll(async () => {
await new Promise((resolve) => server.close(resolve))
})
it('should handle queries correctly', async () => {
const result = await client.query({
query: parse(/* GraphQL */ `
query Greetings {
hello
}
`),
})
expect(result.error).toBeUndefined()
expect(result.errors?.length).toBeFalsy()
expect(result.data).toEqual({
hello: 'Hello Apollo Client!',
})
})
it('should handle subscriptions correctly', async () => {
const observable = client.subscribe({
query: parse(/* GraphQL */ `
subscription Time {
time
}
`),
})
const asyncIterable =
observableToAsyncIterable<
FetchResult<any, Record<string, any>, Record<string, any>>
>(observable)
let i = 0
for await (const result of asyncIterable) {
i++
if (i === 2) {
break
}
expect(result.errors?.length).toBeFalsy()
const date = new Date(result?.data?.time)
expect(date.getFullYear()).toBe(new Date().getFullYear())
}
expect(i).toBe(2)
})
it('should handle file uploads correctly', async () => {
const result = await client.mutate({
mutation: parse(/* GraphQL */ `
mutation readFile($file: File!) {
readFile(file: $file)
}
`),
variables: {
file: new File(['Hello World'], 'file.txt', { type: 'text/plain' }),
},
})
expect(result.errors?.length).toBeFalsy()
expect(result.data).toEqual({
readFile: 'Hello World',
})
})
})
66 changes: 66 additions & 0 deletions packages/urql-exchange/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@graphql-yoga/urql-exchange",
"version": "0.0.0",
"description": "",
"repository": {
"type": "git",
"url": "https://github.com/dotansimha/graphql-yoga.git",
"directory": "packages/urql-exchange"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"scripts": {
"check": "tsc --pretty --noEmit"
},
"keywords": [
"graphql",
"server",
"graphql-yoga",
"apollo"
],
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
"license": "MIT",
"buildOptions": {
"input": "./src/index.ts"
},
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"typescript": {
"definition": "dist/typings/index.d.ts"
},
"publishConfig": {
"directory": "dist",
"access": "public"
},
"dependencies": {
"@graphql-tools/url-loader": "7.12.4",
"@graphql-tools/utils": "8.8.0",
"tslib": "^2.4.0"
},
"devDependencies": {
"@urql/core": "2.6.0",
"wonka": "4.0.15"
},
"peerDependencies": {
"graphql": "^15.2.0 || ^16.0.0",
"@urql/core": "^2.4.3",
"wonka": "^4.0.15"
},
"type": "module"
}
Loading

0 comments on commit 87d1070

Please sign in to comment.