From 1253659e8ed2647da25bb10d2403c03206a8f345 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Sat, 30 Jul 2022 08:26:33 +0200 Subject: [PATCH] feat: add ws example tests --- .../todo-example/client-apollo/package.json | 19 +- .../src/apollo-link/create-ws-apollo-link.ts | 42 +++++ .../todo-example/client-apollo/src/index.tsx | 13 ++ .../todo-example/client-relay/package.json | 3 +- .../src/fetcher/create-ws-fetcher.ts | 56 ++++++ .../todo-example/client-relay/src/index.tsx | 14 +- .../todo-example/client-urql/package.json | 8 +- .../todo-example/client-urql/src/index.tsx | 9 + .../src/urql-client/http-client.ts | 2 - .../client-urql/src/urql-client/ws-client.ts | 46 +++++ .../end2end-tests/end2end.spec.ts | 9 +- packages/todo-example/server-ws/package.json | 46 +---- packages/todo-example/server-ws/src/main.ts | 162 ++++++++++++------ packages/todo-example/server-ws/src/schema.ts | 2 +- .../todo-example/server-ws/src/util/flow.ts | 129 ++++++++++++++ yarn.lock | 5 + 16 files changed, 449 insertions(+), 116 deletions(-) create mode 100644 packages/todo-example/client-apollo/src/apollo-link/create-ws-apollo-link.ts create mode 100644 packages/todo-example/client-relay/src/fetcher/create-ws-fetcher.ts create mode 100644 packages/todo-example/client-urql/src/urql-client/ws-client.ts create mode 100644 packages/todo-example/server-ws/src/util/flow.ts diff --git a/packages/todo-example/client-apollo/package.json b/packages/todo-example/client-apollo/package.json index d256940e..0f4a5879 100644 --- a/packages/todo-example/client-apollo/package.json +++ b/packages/todo-example/client-apollo/package.json @@ -2,15 +2,6 @@ "name": "@n1ru4l/todo-example-client-apollo", "version": "0.1.0", "private": true, - "dependencies": { - "@apollo/client": "3.6.9", - "@n1ru4l/graphql-live-query-patch-jsondiffpatch": "0.8.0", - "@n1ru4l/push-pull-async-iterable-iterator": "3.2.0", - "@n1ru4l/socket-io-graphql-client": "*", - "@repeaterjs/repeater": "3.0.4", - "@app/gql": "link:./src/gql", - "graphql": "16.0.0-experimental-stream-defer.5" - }, "devDependencies": { "@graphql-codegen/cli": "2.8.0", "@graphql-codegen/gql-tag-operations-preset": "1.5.1", @@ -25,7 +16,15 @@ "react-dom": "17.0.2", "socket.io-client": "4.5.1", "todomvc-app-css": "2.4.2", - "vite": "2.9.14" + "vite": "2.9.14", + "@apollo/client": "3.6.9", + "@n1ru4l/graphql-live-query-patch-jsondiffpatch": "0.8.0", + "@n1ru4l/push-pull-async-iterable-iterator": "3.2.0", + "@n1ru4l/socket-io-graphql-client": "*", + "@repeaterjs/repeater": "3.0.4", + "@app/gql": "link:./src/gql", + "graphql": "16.0.0-experimental-stream-defer.5", + "graphql-ws": "5.9.1" }, "scripts": { "start": "vite", diff --git a/packages/todo-example/client-apollo/src/apollo-link/create-ws-apollo-link.ts b/packages/todo-example/client-apollo/src/apollo-link/create-ws-apollo-link.ts new file mode 100644 index 00000000..710c622d --- /dev/null +++ b/packages/todo-example/client-apollo/src/apollo-link/create-ws-apollo-link.ts @@ -0,0 +1,42 @@ +// for Apollo Client v3 older than v3.5.10: +import { + ApolloLink, + Operation, + FetchResult, + Observable, +} from "@apollo/client/core"; +import { print } from "graphql"; +import { createClient, Client } from "graphql-ws"; +import { makeAsyncIterableIteratorFromSink } from "@n1ru4l/push-pull-async-iterable-iterator"; +import { applySourceToSink } from "./shared"; + +class GraphQLWsLink extends ApolloLink { + private client: Client; + constructor(url: string) { + super(); + this.client = createClient({ + url, + }); + } + + public request(operation: Operation): Observable { + return new Observable((sink) => { + const source = makeAsyncIterableIteratorFromSink((sink) => { + return this.client.subscribe( + { ...operation, query: print(operation.query) }, + { + next: sink.next.bind(sink), + complete: sink.complete.bind(sink), + error: sink.error.bind(sink), + } + ); + }); + + return applySourceToSink(source, sink); + }); + } +} + +export function createWSApolloLink(url: string) { + return new GraphQLWsLink(url); +} diff --git a/packages/todo-example/client-apollo/src/index.tsx b/packages/todo-example/client-apollo/src/index.tsx index 529081a2..28b94450 100644 --- a/packages/todo-example/client-apollo/src/index.tsx +++ b/packages/todo-example/client-apollo/src/index.tsx @@ -37,6 +37,19 @@ const Root = (): React.ReactElement | null => { ); } ); + } else if (params.get("ws")) { + let host = params.get("host") ?? undefined; + import("./apollo-link/create-ws-apollo-link").then( + async ({ createWSApolloLink }) => { + setClient( + createApolloClient( + createWSApolloLink( + (host ?? `ws://${window.location.host}`) + "/graphql" + ) + ) + ); + } + ); } else { import("./apollo-link/create-socket-io-apollo-link").then( async ({ createSocketIOApolloLink }) => { diff --git a/packages/todo-example/client-relay/package.json b/packages/todo-example/client-relay/package.json index f6767109..61ef1b7f 100644 --- a/packages/todo-example/client-relay/package.json +++ b/packages/todo-example/client-relay/package.json @@ -28,7 +28,8 @@ "todomvc-app-css": "2.4.2", "classnames": "2.3.1", "vite": "2.9.14", - "vite-plugin-babel-macros": "1.0.6" + "vite-plugin-babel-macros": "1.0.6", + "graphql-ws": "5.9.1" }, "scripts": { "start": "vite", diff --git a/packages/todo-example/client-relay/src/fetcher/create-ws-fetcher.ts b/packages/todo-example/client-relay/src/fetcher/create-ws-fetcher.ts new file mode 100644 index 00000000..0053cc27 --- /dev/null +++ b/packages/todo-example/client-relay/src/fetcher/create-ws-fetcher.ts @@ -0,0 +1,56 @@ +import { + GraphQLResponse, + Observable, + RequestParameters, + Variables, +} from "relay-runtime"; +import { createClient } from "graphql-ws"; +import { Repeater } from "@repeaterjs/repeater"; +import { applySourceToSink } from "./shared"; +import { makeAsyncIterableIteratorFromSink } from "@n1ru4l/push-pull-async-iterable-iterator"; + +function makeEventStreamSource(url: string) { + return new Repeater(async (push, end) => { + const eventsource = new EventSource(url); + eventsource.onmessage = function (event) { + const data = JSON.parse(event.data); + push(data); + if (eventsource.readyState === 2) { + end(); + } + }; + eventsource.onerror = function (event) { + console.log("Error", event); + end(new Error("Check the console bruv.")); + }; + await end; + + eventsource.close(); + }); +} + +export function createWSFetcher(url: string) { + const client = createClient({ url }); + return ( + request: RequestParameters, + variables: Variables + ): Observable => { + if (!request.text) throw new Error("Missing document."); + const { text: operation, name } = request; + + return Observable.create((sink) => { + const source = makeAsyncIterableIteratorFromSink((sink) => { + return client.subscribe( + { variables, query: operation }, + { + next: sink.next.bind(sink), + complete: sink.complete.bind(sink), + error: sink.error.bind(sink), + } + ); + }); + + return applySourceToSink(source, sink); + }); + }; +} diff --git a/packages/todo-example/client-relay/src/index.tsx b/packages/todo-example/client-relay/src/index.tsx index 6f002990..75e4ef26 100644 --- a/packages/todo-example/client-relay/src/index.tsx +++ b/packages/todo-example/client-relay/src/index.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; -import { createSocketIOGraphQLClient } from "@n1ru4l/socket-io-graphql-client"; -import type { Environment, GraphQLResponse } from "relay-runtime"; +import type { Environment } from "relay-runtime"; import "todomvc-app-css/index.css"; import { TodoApplication } from "./TodoApplication"; import { createRelayEnvironment } from "./createRelayEnvironment"; @@ -25,6 +24,17 @@ const Root = () => { ) ); }); + } else if (params.get("ws")) { + const host = params.get("host") ?? undefined; + import("./fetcher/create-ws-fetcher").then(({ createWSFetcher }) => { + setEnvironment( + createRelayEnvironment( + createWSFetcher( + (host ?? `ws://${window.location.host}`) + "/graphql" + ) + ) + ); + }); } else { import("./fetcher/create-socket-io-fetcher").then( ({ createSocketIOFetcher }) => { diff --git a/packages/todo-example/client-urql/package.json b/packages/todo-example/client-urql/package.json index feb2f154..132ecbe7 100644 --- a/packages/todo-example/client-urql/package.json +++ b/packages/todo-example/client-urql/package.json @@ -2,10 +2,6 @@ "name": "@n1ru4l/todo-example-client-urql", "version": "0.1.0", "private": true, - "dependencies": { - "urql": "2.2.2", - "graphql": "16.0.0-experimental-stream-defer.5" - }, "devDependencies": { "@n1ru4l/graphql-live-query-patch-jsondiffpatch": "0.8.0", "@repeaterjs/repeater": "3.0.4", @@ -24,7 +20,9 @@ "react-dom": "17.0.2", "socket.io-client": "4.5.1", "todomvc-app-css": "2.4.2", - "vite": "2.9.14" + "vite": "2.9.14", + "urql": "2.2.2", + "graphql": "16.0.0-experimental-stream-defer.5" }, "scripts": { "start": "vite", diff --git a/packages/todo-example/client-urql/src/index.tsx b/packages/todo-example/client-urql/src/index.tsx index 89ee5fc5..14e9ef6a 100644 --- a/packages/todo-example/client-urql/src/index.tsx +++ b/packages/todo-example/client-urql/src/index.tsx @@ -21,6 +21,15 @@ const Root = (): ReactElement | null => { ) ); }); + } else if (params.get("ws")) { + let host = params.get("host") ?? undefined; + import("./urql-client/ws-client").then(async ({ createUrqlClient }) => { + setClient( + createUrqlClient( + (host ?? `ws://${window.location.host}`) + "/graphql" + ) + ); + }); } else { import("./urql-client/socket-io-client").then( async ({ createUrqlClient }) => { diff --git a/packages/todo-example/client-urql/src/urql-client/http-client.ts b/packages/todo-example/client-urql/src/urql-client/http-client.ts index 1f110829..2a152b3a 100644 --- a/packages/todo-example/client-urql/src/urql-client/http-client.ts +++ b/packages/todo-example/client-urql/src/urql-client/http-client.ts @@ -8,8 +8,6 @@ import { import { getOperationAST } from "graphql"; import { isLiveQueryOperationDefinitionNode } from "@n1ru4l/graphql-live-query"; import { Repeater } from "@repeaterjs/repeater"; -import { applyLiveQueryJSONPatch } from "@n1ru4l/graphql-live-query-patch-json-patch"; -import { applyAsyncIterableIteratorToSink } from "@n1ru4l/push-pull-async-iterable-iterator"; import { ExecutionLivePatchResult } from "@n1ru4l/graphql-live-query-patch"; import { applySourceToSink } from "./shared"; diff --git a/packages/todo-example/client-urql/src/urql-client/ws-client.ts b/packages/todo-example/client-urql/src/urql-client/ws-client.ts new file mode 100644 index 00000000..8c783ab5 --- /dev/null +++ b/packages/todo-example/client-urql/src/urql-client/ws-client.ts @@ -0,0 +1,46 @@ +import { + Client, + subscriptionExchange, + fetchExchange, + cacheExchange, + dedupExchange, + ExecutionResult, +} from "urql"; +import { createClient } from "graphql-ws"; +import { applySourceToSink } from "./shared"; +import { makeAsyncIterableIteratorFromSink } from "@n1ru4l/push-pull-async-iterable-iterator"; + +export const createUrqlClient = (url: string) => { + const client = createClient({ url }); + return new Client({ + url: "noop", + exchanges: [ + cacheExchange, + dedupExchange, + subscriptionExchange({ + forwardSubscription(operation) { + return { + subscribe: (sink) => { + const source = makeAsyncIterableIteratorFromSink((sink) => { + return client.subscribe( + { ...operation, query: operation.query }, + { + next: sink.next.bind(sink), + complete: sink.complete.bind(sink), + error: sink.error.bind(sink), + } + ); + }); + + return { + unsubscribe: applySourceToSink(source, sink), + }; + }, + }; + }, + enableAllOperations: true, + }), + fetchExchange, + ], + }); +}; diff --git a/packages/todo-example/end2end-tests/end2end.spec.ts b/packages/todo-example/end2end-tests/end2end.spec.ts index 5ac91817..c5db7861 100644 --- a/packages/todo-example/end2end-tests/end2end.spec.ts +++ b/packages/todo-example/end2end-tests/end2end.spec.ts @@ -4,6 +4,7 @@ import fastifyStatic from "fastify-static"; import { createServer as createSocketIOServer } from "../server-socket-io/src/index.js"; import { createServer as createHTTPServer } from "../server-helix/src/main.js"; import { createServer as createYogaServer } from "../server-yoga/src/main.js"; +import { createServer as createWSServer } from "../server-ws/src/main.js"; import * as path from "path"; @@ -17,9 +18,13 @@ describe.each([ ["GraphQL over Socket.io", "socket.io", createSocketIOServer], ["GraphQL over SSE (Helix)", "sse", createHTTPServer], ["GraphQL over SSE (Yoga)", "sse", createYogaServer], + ["GraphQL over WebSocket (graphql-ws)", "ws", createWSServer], ])("%s", (_, protocol, createServer) => { const apiPort = 6167; - const apiAddress = `http://localhost:${apiPort}`; + const apiAddress = + protocol !== "ws" + ? `http://localhost:${apiPort}` + : `ws://localhost:${apiPort}`; const staticPort = 6168; const testPage = `http://localhost:${staticPort}?${protocol}=true&host=${encodeURIComponent( @@ -93,7 +98,7 @@ describe.each([ ).map((element: any) => element.innerHTML); }); expect(todos).toEqual(["foo", "Do the laundry"]); - }); + }, 100_000); it("can complete a todo", async () => { page = await browser.newPage(); diff --git a/packages/todo-example/server-ws/package.json b/packages/todo-example/server-ws/package.json index 8b8a9684..8d5bfeea 100644 --- a/packages/todo-example/server-ws/package.json +++ b/packages/todo-example/server-ws/package.json @@ -3,8 +3,8 @@ "version": "0.1.2", "private": true, "dependencies": { - "@n1ru4l/in-memory-live-query-store": "*", - "@n1ru4l/socket-io-graphql-server": "*", + "@n1ru4l/in-memory-live-query-store": "0.10.0", + "@n1ru4l/graphql-live-query-patch-jsondiffpatch": "0.8.0", "graphql": "16.0.0-experimental-stream-defer.5", "graphql-ws": "5.9.1", "ws": "8.8.0" @@ -19,47 +19,5 @@ "write:schema": "ts-node scripts/write-graphql-schema.ts", "build": "tsc", "start": "ts-node-dev src/main.ts" - }, - "type": "module", - "main": "dist/cjs/index.js", - "module": "dist/esm/index.js", - "typings": "dist/typings/index.d.ts", - "typescript": { - "definition": "dist/typings/index.d.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" - } - }, - "./*": { - "require": { - "types": "./dist/typings/*.d.ts", - "default": "./dist/cjs/*.js" - }, - "import": { - "types": "./dist/typings/*.d.ts", - "default": "./dist/esm/*.js" - }, - "default": { - "types": "./dist/typings/*.d.ts", - "default": "./dist/esm/*.js" - } - }, - "./package.json": "./package.json" - }, - "publishConfig": { - "directory": "dist", - "access": "public" } } diff --git a/packages/todo-example/server-ws/src/main.ts b/packages/todo-example/server-ws/src/main.ts index 71a7490e..0c4850a2 100644 --- a/packages/todo-example/server-ws/src/main.ts +++ b/packages/todo-example/server-ws/src/main.ts @@ -1,58 +1,122 @@ -import { ExecutionArgs, parse, specifiedRules, validate } from "graphql"; +import { + execute as defaultExecuteImplementation, + ExecutionArgs, + parse, + specifiedRules, + validate, +} from "graphql"; import { useServer } from "graphql-ws/lib/use/ws"; import { InMemoryLiveQueryStore } from "@n1ru4l/in-memory-live-query-store"; import { NoLiveMixedWithDeferStreamRule } from "@n1ru4l/graphql-live-query"; -import { Server } from "ws"; -import { schema } from "./schema.js"; +import { applyLiveQueryJSONDiffPatchGenerator } from "@n1ru4l/graphql-live-query-patch-jsondiffpatch"; +import { Server, WebSocket } from "ws"; +import { schema } from "./schema"; +import { flow } from "./util/flow"; -const liveQueryStore = new InMemoryLiveQueryStore(); -const rootValue = { - todos: new Map(), -}; -const contextValue = { - liveQueryStore, +const parsePortSafe = (port: null | undefined | string) => { + if (!port) { + return null; + } + const parsedPort = parseInt(port, 10); + if (Number.isNaN(parsedPort)) { + return null; + } + return parsedPort; }; -rootValue.todos.set("1", { - id: "1", - content: "foo", - isCompleted: false, -}); - -const wsServer = new Server({ - port: 3415, - path: "/graphql", -}); - -useServer( - { - schema, - execute: liveQueryStore.execute, - onSubscribe: (_, msg) => { - const args: ExecutionArgs = { - schema, - operationName: msg.payload.operationName, - document: parse(msg.payload.query), - variableValues: msg.payload.variables, - contextValue, - rootValue, - }; - - const errors = validate(args.schema, args.document, [ - ...specifiedRules, - NoLiveMixedWithDeferStreamRule, - ]); - - if (errors.length) return errors; - - return args; +export const createServer = async ({ port = 3001 }: { port?: number }) => { + const liveQueryStore = new InMemoryLiveQueryStore(); + const rootValue = { + todos: new Map(), + }; + const contextValue = { + liveQueryStore, + }; + + rootValue.todos.set("1", { + id: "1", + content: "foo", + isCompleted: false, + }); + + const wsServer = new Server({ + port, + path: "/graphql", + }); + + const execute = flow( + liveQueryStore.makeExecute(defaultExecuteImplementation), + applyLiveQueryJSONDiffPatchGenerator + ); + + useServer( + { + schema, + execute, + onSubscribe: (_, msg) => { + const args: ExecutionArgs = { + schema, + operationName: msg.payload.operationName, + document: parse(msg.payload.query), + variableValues: msg.payload.variables, + contextValue, + rootValue, + }; + + const errors = validate(args.schema, args.document, [ + ...specifiedRules, + NoLiveMixedWithDeferStreamRule, + ]); + + if (errors.length) return errors; + + return args; + }, }, - }, - wsServer -); + wsServer + ); + + wsServer.on("listening", () => { + console.log(`GraphQL server is running on port ${port}.`); + }); + + // Graceful shutdown stuff :) + // Ensure connections are closed when shutting down is received + const connections = new Set(); + wsServer.on("connection", (connection) => { + connections.add(connection); + connection.on("close", () => { + connections.delete(connection); + }); + }); + + return async () => { + await new Promise((resolve, reject) => { + wsServer.close((err) => { + if (err) return reject(err); + resolve(); + }); + for (const connection of connections) { + connection.close(); + } + }); + }; +}; + +if (require.main === module) { + let isShuttingDown = false; -const port = process.env.PORT || 3001; + (async () => { + const port = parsePortSafe(process.env.PORT) ?? 3001; + const destroy = await createServer({ port }); + console.log("Listening on http://localhost:" + port); -wsServer.on("listening", () => { - console.log(`GraphQL server is running on port ${port}.`); -}); + process.on("SIGINT", () => { + if (isShuttingDown) { + return; + } + isShuttingDown = true; + destroy(); + }); + })(); +} diff --git a/packages/todo-example/server-ws/src/schema.ts b/packages/todo-example/server-ws/src/schema.ts index 6f7cf263..7bcfc50d 100644 --- a/packages/todo-example/server-ws/src/schema.ts +++ b/packages/todo-example/server-ws/src/schema.ts @@ -49,7 +49,7 @@ const GraphQLQueryType = new GraphQLObjectType({ type: new GraphQLNonNull( new GraphQLList(new GraphQLNonNull(GraphQLTodoType)) ), - resolve: (root, args, context) => Array.from(root.todos.values()), + resolve: (root) => Array.from(root.todos.values()), }, }, }); diff --git a/packages/todo-example/server-ws/src/util/flow.ts b/packages/todo-example/server-ws/src/util/flow.ts new file mode 100644 index 00000000..2258f49a --- /dev/null +++ b/packages/todo-example/server-ws/src/util/flow.ts @@ -0,0 +1,129 @@ +/** + * + * @source https://github.com/gcanti/fp-ts/blob/4e460a6367bd1454fc65359c3ea22fe671722e45/src/function.ts#L118-L241 + */ +export function flow, B>( + ab: (...a: A) => B +): (...a: A) => B; +export function flow, B, C>( + ab: (...a: A) => B, + bc: (b: B) => C +): (...a: A) => C; +export function flow, B, C, D>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D +): (...a: A) => D; +export function flow, B, C, D, E>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E +): (...a: A) => E; +export function flow, B, C, D, E, F>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F +): (...a: A) => F; +export function flow, B, C, D, E, F, G>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G +): (...a: A) => G; +export function flow, B, C, D, E, F, G, H>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H +): (...a: A) => H; +export function flow, B, C, D, E, F, G, H, I>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I +): (...a: A) => I; +export function flow< + A extends ReadonlyArray, + B, + C, + D, + E, + F, + G, + H, + I, + J +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J +): (...a: A) => J; +export function flow( + ab: Function, + bc?: Function, + cd?: Function, + de?: Function, + ef?: Function, + fg?: Function, + gh?: Function, + hi?: Function, + ij?: Function +): unknown { + switch (arguments.length) { + case 1: + return ab; + case 2: + return function (this: unknown) { + return bc!(ab.apply(this, arguments)); + }; + case 3: + return function (this: unknown) { + return cd!(bc!(ab.apply(this, arguments))); + }; + case 4: + return function (this: unknown) { + return de!(cd!(bc!(ab.apply(this, arguments)))); + }; + case 5: + return function (this: unknown) { + return ef!(de!(cd!(bc!(ab.apply(this, arguments))))); + }; + case 6: + return function (this: unknown) { + return fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments)))))); + }; + case 7: + return function (this: unknown) { + return gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments))))))); + }; + case 8: + return function (this: unknown) { + return hi!(gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments)))))))); + }; + case 9: + return function (this: unknown) { + return ij!( + hi!(gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments)))))))) + ); + }; + } + return; +} diff --git a/yarn.lock b/yarn.lock index 87656967..8c726598 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1964,6 +1964,11 @@ globby "^11.0.0" read-yaml-file "^1.1.0" +"@n1ru4l/graphql-live-query@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" + integrity sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg== + "@n1ru4l/push-pull-async-iterable-iterator@3.2.0", "@n1ru4l/push-pull-async-iterable-iterator@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz#c15791112db68dd9315d329d652b7e797f737655"