graphql#getOperationAST TypeScript Examples

The following examples show how to use graphql#getOperationAST. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
function createMeshApolloRequestHandler(options: MeshApolloRequestHandlerOptions): RequestHandler {
  return function meshApolloRequestHandler(operation: Operation): Observable<FetchResult> {
    const operationAst = getOperationAST(operation.query, operation.operationName);
    if (!operationAst) {
      throw new Error('GraphQL operation not found');
    }
    const operationFn = operationAst.operation === 'subscription' ? options.subscribe : options.execute;
    return new Observable(observer => {
      Promise.resolve()
        .then(async () => {
          const results = await operationFn(
            operation.query,
            operation.variables,
            operation.getContext(),
            ROOT_VALUE,
            operation.operationName
          );
          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);
          }
        });
    });
  };
}
Example #2
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
export default function useMeshLiveQuery(options: MeshPluginOptions<YamlConfig.LiveQueryConfig>): Plugin {
  const liveQueryInvalidationFactoryMap = new Map<string, ResolverDataBasedFactory<string>[]>();
  options.logger.debug(`Creating Live Query Store`);
  const liveQueryStore = new InMemoryLiveQueryStore({
    includeIdentifierExtension: true,
  });
  options.liveQueryInvalidations?.forEach(liveQueryInvalidation => {
    const rawInvalidationPaths = liveQueryInvalidation.invalidate;
    const factories = rawInvalidationPaths.map(rawInvalidationPath =>
      getInterpolatedStringFactory(rawInvalidationPath)
    );
    liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
  });
  return useEnvelop(
    envelop({
      plugins: [
        useLiveQuery({ liveQueryStore }),
        {
          onExecute(onExecuteParams) {
            if (!onExecuteParams.args.schema.getDirective('live')) {
              options.logger.warn(`You have to add @live directive to additionalTypeDefs to enable Live Queries
See more at https://www.graphql-mesh.com/docs/recipes/live-queries`);
            }
            return {
              onExecuteDone({ args: executionArgs, result }) {
                queueMicrotask(() => {
                  const { schema, document, operationName, rootValue, contextValue } = executionArgs;
                  const operationAST = getOperationAST(document, operationName);
                  if (!operationAST) {
                    throw new Error(`Operation couldn't be found`);
                  }
                  const typeInfo = new TypeInfo(schema);
                  visit(
                    operationAST,
                    visitWithTypeInfo(typeInfo, {
                      Field: () => {
                        const parentType = typeInfo.getParentType();
                        const fieldDef = typeInfo.getFieldDef();
                        const path = `${parentType.name}.${fieldDef.name}`;
                        if (liveQueryInvalidationFactoryMap.has(path)) {
                          const invalidationPathFactories = liveQueryInvalidationFactoryMap.get(path);
                          const invalidationPaths = invalidationPathFactories.map(invalidationPathFactory =>
                            invalidationPathFactory({
                              root: rootValue,
                              context: contextValue,
                              env: process.env,
                              result,
                            })
                          );
                          liveQueryStore
                            .invalidate(invalidationPaths)
                            .catch((e: Error) => options.logger.warn(`Invalidation failed for ${path}: ${e.message}`));
                        }
                      },
                    })
                  );
                });
              },
            };
          },
        },
      ],
    })
  );
}
Example #3
Source File: get-mesh.ts    From graphql-mesh with MIT License 5 votes vote down vote up
memoizedGetOperationType = memoize1((document: DocumentNode) => {
  const operationAST = getOperationAST(document, undefined);
  if (!operationAST) {
    throw new Error('Must provide document with a valid operation');
  }
  return operationAST.operation;
})
Example #4
Source File: handler.ts    From graphql-sse with MIT License 4 votes vote down vote up
/**
 * Makes a Protocol complient HTTP GraphQL server  handler. The handler can
 * be used with your favourite server library.
 *
 * Read more about the Protocol in the PROTOCOL.md documentation file.
 *
 * @category Server
 */
export function createHandler<
  Request extends IncomingMessage = IncomingMessage,
  Response extends ServerResponse = ServerResponse,
>(options: HandlerOptions<Request, Response>): Handler<Request, Response> {
  const {
    schema,
    context,
    validate = graphqlValidate,
    execute = graphqlExecute,
    subscribe = graphqlSubscribe,
    authenticate = function extractOrCreateStreamToken(req) {
      const headerToken =
        req.headers[TOKEN_HEADER_KEY] || req.headers['x-graphql-stream-token']; // @deprecated >v1.0.0
      if (headerToken)
        return Array.isArray(headerToken) ? headerToken.join('') : headerToken;

      const urlToken = new URL(
        req.url ?? '',
        'http://localhost/',
      ).searchParams.get(TOKEN_QUERY_KEY);
      if (urlToken) return urlToken;

      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
        const r = (Math.random() * 16) | 0,
          v = c == 'x' ? r : (r & 0x3) | 0x8;
        return v.toString(16);
      });
    },
    onConnecting,
    onConnected,
    onSubscribe,
    onOperation,
    onNext,
    onComplete,
    onDisconnect,
  } = options;

  const streams: Record<string, Stream> = {};

  function createStream(token: string | null): Stream<Request, Response> {
    let request: Request | null = null,
      response: Response | null = null,
      pinger: ReturnType<typeof setInterval>,
      disposed = false;
    const pendingMsgs: string[] = [];
    const ops: Record<
      string,
      AsyncGenerator<unknown> | AsyncIterable<unknown> | null
    > = {};

    function write(msg: unknown) {
      return new Promise<boolean>((resolve, reject) => {
        if (disposed || !response || !response.writable) return resolve(false);
        response.write(msg, (err) => {
          if (err) return reject(err);
          resolve(true);
        });
      });
    }

    async function emit<E extends StreamEvent>(
      event: E,
      data: StreamData<E> | StreamDataForID<E>,
    ): Promise<void> {
      let msg = `event: ${event}`;
      if (data) msg += `\ndata: ${JSON.stringify(data)}`;
      msg += '\n\n';

      const wrote = await write(msg);
      if (!wrote) pendingMsgs.push(msg);
    }

    async function dispose() {
      if (disposed) return;
      disposed = true;

      // make room for another potential stream while this one is being disposed
      if (typeof token === 'string') delete streams[token];

      // complete all operations and flush messages queue before ending the stream
      for (const op of Object.values(ops)) {
        if (isAsyncGenerator(op)) await op.return(undefined);
      }
      while (pendingMsgs.length) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const msg = pendingMsgs.shift()!;
        await write(msg);
      }

      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      response!.end(); // response must exist at this point
      response = null;
      clearInterval(pinger);

      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      onDisconnect?.(request!); // request must exist at this point
      request = null;
    }

    return {
      get open() {
        return disposed || Boolean(response);
      },
      ops,
      async use(req, res) {
        request = req;
        response = res;

        req.socket.setTimeout(0);
        req.socket.setNoDelay(true);
        req.socket.setKeepAlive(true);

        res.once('close', dispose);
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('X-Accel-Buffering', 'no');
        if (req.httpVersionMajor < 2) res.setHeader('Connection', 'keep-alive');
        res.flushHeaders();

        // write an empty message because some browsers (like Firefox and Safari)
        // dont accept the header flush
        await write(':\n\n');

        // ping client every 12 seconds to keep the connection alive
        pinger = setInterval(() => write(':\n\n'), 12_000);

        while (pendingMsgs.length) {
          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
          const msg = pendingMsgs.shift()!;
          const wrote = await write(msg);
          if (!wrote) throw new Error('Unable to flush messages');
        }

        await onConnected?.(req);
      },
      async from(operationReq, args, result, opId) {
        if (isAsyncIterable(result)) {
          /** multiple emitted results */
          for await (let part of result) {
            const maybeResult = await onNext?.(operationReq, args, part);
            if (maybeResult) part = maybeResult;

            await emit(
              'next',
              opId
                ? {
                    id: opId,
                    payload: part,
                  }
                : part,
            );
          }
        } else {
          /** single emitted result */
          const maybeResult = await onNext?.(operationReq, args, result);
          if (maybeResult) result = maybeResult;

          await emit(
            'next',
            opId
              ? {
                  id: opId,
                  payload: result,
                }
              : result,
          );
        }

        await emit('complete', opId ? { id: opId } : null);

        // end on complete when no operation id is present
        // because distinct event streams are used for each operation
        if (!opId) await dispose();
        else delete ops[opId];

        await onComplete?.(operationReq, args);
      },
    };
  }

  async function prepare(
    req: Request,
    res: Response,
    params: RequestParams,
  ): Promise<[args: ExecutionArgs, perform: () => OperationResult] | void> {
    let args: ExecutionArgs, operation: OperationTypeNode;

    const maybeExecArgs = await onSubscribe?.(req, res, params);
    if (maybeExecArgs) args = maybeExecArgs;
    else {
      // you either provide a schema dynamically through
      // `onSubscribe` or you set one up during the server setup
      if (!schema) throw new Error('The GraphQL schema is not provided');

      const { operationName, variables } = params;
      let { query } = params;

      if (typeof query === 'string') {
        try {
          query = parse(query);
        } catch {
          res.writeHead(400, 'GraphQL query syntax error').end();
          return;
        }
      }

      const argsWithoutSchema = {
        operationName,
        document: query,
        variableValues: variables,
      };
      args = {
        ...argsWithoutSchema,
        schema:
          typeof schema === 'function'
            ? await schema(req, argsWithoutSchema)
            : schema,
      };
    }

    try {
      const ast = getOperationAST(args.document, args.operationName);
      if (!ast) throw null;
      operation = ast.operation;
    } catch {
      res.writeHead(400, 'Unable to detect operation AST').end();
      return;
    }

    // mutations cannot happen over GETs as per the spec
    // Read more: https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#get
    if (operation === 'mutation' && req.method === 'GET') {
      res
        .writeHead(405, 'Cannot perform mutations over GET', {
          Allow: 'POST',
        })
        .end();
      return;
    }

    if (!('contextValue' in args))
      args.contextValue =
        typeof context === 'function' ? await context(req, args) : context;

    // we validate after injecting the context because the process of
    // reporting the validation errors might need the supplied context value
    const validationErrs = validate(args.schema, args.document);
    if (validationErrs.length) {
      if (req.headers.accept === 'text/event-stream') {
        // accept the request and emit the validation error in event streams,
        // promoting graceful GraphQL error reporting
        // Read more: https://www.w3.org/TR/eventsource/#processing-model
        // Read more: https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#document-validation
        return [
          args,
          function perform() {
            return { errors: validationErrs };
          },
        ];
      }

      res
        .writeHead(400, {
          'Content-Type':
            req.headers.accept === 'application/json'
              ? 'application/json; charset=utf-8'
              : 'application/graphql+json; charset=utf-8',
        })
        .write(JSON.stringify({ errors: validationErrs }));
      res.end();
      return;
    }

    return [
      args,
      async function perform() {
        let result =
          operation === 'subscription' ? subscribe(args) : execute(args);

        const maybeResult = await onOperation?.(req, res, args, result);
        if (maybeResult) result = maybeResult;

        return result;
      },
    ];
  }

  return async function handler(req: Request, res: Response, body: unknown) {
    // authenticate first and acquire unique identification token
    const token = await authenticate(req, res);
    if (res.writableEnded) return;
    if (typeof token !== 'string') throw new Error('Token was not supplied');

    const accept = req.headers.accept ?? '*/*';

    const stream = streams[token];

    if (accept === 'text/event-stream') {
      // if event stream is not registered, process it directly.
      // this means that distinct connections are used for graphql operations
      if (!stream) {
        let params;
        try {
          params = await parseReq(req, body);
        } catch (err) {
          res.writeHead(400, err.message).end();
          return;
        }

        const distinctStream = createStream(null);

        // reserve space for the operation
        distinctStream.ops[''] = null;

        const prepared = await prepare(req, res, params);
        if (res.writableEnded) return;
        if (!prepared)
          throw new Error(
            "Operation preparation didn't respond, yet it was not prepared",
          );
        const [args, perform] = prepared;

        const result = await perform();
        if (res.writableEnded) {
          if (isAsyncGenerator(result)) result.return(undefined);
          return; // `onOperation` responded
        }

        if (isAsyncIterable(result)) distinctStream.ops[''] = result;

        await onConnecting?.(req, res);
        if (res.writableEnded) return;
        await distinctStream.use(req, res);
        await distinctStream.from(req, args, result);
        return;
      }

      // open stream cant exist, only one per token is allowed
      if (stream.open) {
        res.writeHead(409, 'Stream already open').end();
        return;
      }

      await onConnecting?.(req, res);
      if (res.writableEnded) return;
      await stream.use(req, res);
      return;
    }

    if (req.method === 'PUT') {
      // method PUT prepares a stream for future incoming connections.

      if (!['*/*', 'text/plain'].includes(accept)) {
        res.writeHead(406).end();
        return;
      }

      // streams mustnt exist if putting new one
      if (stream) {
        res.writeHead(409, 'Stream already registered').end();
        return;
      }

      streams[token] = createStream(token);
      res
        .writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' })
        .write(token);
      res.end();
      return;
    } else if (req.method === 'DELETE') {
      // method DELETE completes an existing operation streaming in streams

      // streams must exist when completing operations
      if (!stream) {
        res.writeHead(404, 'Stream not found').end();
        return;
      }

      const opId = new URL(req.url ?? '', 'http://localhost/').searchParams.get(
        'operationId',
      );
      if (!opId) {
        res.writeHead(400, 'Operation ID is missing').end();
        return;
      }

      const op = stream.ops[opId];
      if (isAsyncGenerator(op)) op.return(undefined);
      delete stream.ops[opId]; // deleting the operation means no further activity should take place

      res.writeHead(200).end();
      return;
    } else if (req.method !== 'GET' && req.method !== 'POST') {
      // only POSTs and GETs are accepted at this point
      res.writeHead(405, undefined, { Allow: 'GET, POST, PUT, DELETE' }).end();
      return;
    } else if (!stream) {
      // for all other requests, streams must exist to attach the result onto
      res.writeHead(404, 'Stream not found').end();
      return;
    }

    if (
      !['*/*', 'application/graphql+json', 'application/json'].includes(accept)
    ) {
      res.writeHead(406).end();
      return;
    }

    let params;
    try {
      params = await parseReq(req, body);
    } catch (err) {
      res.writeHead(400, err.message).end();
      return;
    }

    const opId = String(params.extensions?.operationId ?? '');
    if (!opId) {
      res.writeHead(400, 'Operation ID is missing').end();
      return;
    }
    if (opId in stream.ops) {
      res.writeHead(409, 'Operation with ID already exists').end();
      return;
    }
    // reserve space for the operation through ID
    stream.ops[opId] = null;

    const prepared = await prepare(req, res, params);
    if (res.writableEnded) return;
    if (!prepared)
      throw new Error(
        "Operation preparation didn't respond, yet it was not prepared",
      );
    const [args, perform] = prepared;

    // operation might have completed before prepared
    if (!(opId in stream.ops)) {
      res.writeHead(204).end();
      return;
    }

    const result = await perform();
    if (res.writableEnded) {
      if (isAsyncGenerator(result)) result.return(undefined);
      delete stream.ops[opId];
      return; // `onOperation` responded
    }

    // operation might have completed before performed
    if (!(opId in stream.ops)) {
      if (isAsyncGenerator(result)) result.return(undefined);
      res.writeHead(204).end();
      return;
    }

    if (isAsyncIterable(result)) stream.ops[opId] = result;

    res.writeHead(202).end();

    // streaming to an empty reservation is ok (will be flushed on connect)
    await stream.from(req, args, result, opId);
  };
}
Example #5
Source File: index.ts    From hono with MIT License 4 votes vote down vote up
graphqlServer = (options: Options) => {
  const schema = options.schema
  const rootValue = options.rootValue
  const pretty = options.pretty ?? false
  const validationRules = options.validationRules ?? []
  // const showGraphiQL = options.graphiql ?? false

  return async (c: Context, next: Next) => {
    // GraphQL HTTP only supports GET and POST methods.
    if (c.req.method !== 'GET' && c.req.method !== 'POST') {
      return c.json(errorMessages(['GraphQL only supports GET and POST requests.']), 405, {
        Allow: 'GET, POST',
      })
    }

    let params: GraphQLParams
    try {
      params = await getGraphQLParams(c.req)
    } catch (e) {
      if (e instanceof Error) {
        console.error(`${e.stack || e.message}`)
        return c.json(errorMessages([e.message], [e]), 400)
      }
      throw e
    }

    const { query, variables, operationName } = params

    if (query == null) {
      return c.json(errorMessages(['Must provide query string.']), 400)
    }

    const schemaValidationErrors = validateSchema(schema)
    if (schemaValidationErrors.length > 0) {
      // Return 500: Internal Server Error if invalid schema.
      return c.json(
        errorMessages(['GraphQL schema validation error.'], schemaValidationErrors),
        500
      )
    }

    let documentAST: DocumentNode
    try {
      documentAST = parse(new Source(query, 'GraphQL request'))
    } catch (syntaxError: unknown) {
      // Return 400: Bad Request if any syntax errors errors exist.
      if (syntaxError instanceof Error) {
        console.error(`${syntaxError.stack || syntaxError.message}`)
        const e = new GraphQLError(syntaxError.message, {
          originalError: syntaxError,
        })
        return c.json(errorMessages(['GraphQL syntax error.'], [e]), 400)
      }
      throw syntaxError
    }

    // Validate AST, reporting any errors.
    const validationErrors = validate(schema, documentAST, [...specifiedRules, ...validationRules])

    if (validationErrors.length > 0) {
      // Return 400: Bad Request if any validation errors exist.
      return c.json(errorMessages(['GraphQL validation error.'], validationErrors), 400)
    }

    if (c.req.method === 'GET') {
      // Determine if this GET request will perform a non-query.
      const operationAST = getOperationAST(documentAST, operationName)
      if (operationAST && operationAST.operation !== 'query') {
        /*
        Now , does not support GraphiQL
        if (showGraphiQL) {
          //return respondWithGraphiQL(response, graphiqlOptions, params)
        }
        */

        // Otherwise, report a 405: Method Not Allowed error.
        return c.json(
          errorMessages([
            `Can only perform a ${operationAST.operation} operation from a POST request.`,
          ]),
          405,
          { Allow: 'POST' }
        )
      }
    }

    let result: FormattedExecutionResult

    try {
      result = await execute({
        schema,
        document: documentAST,
        rootValue,
        variableValues: variables,
        operationName: operationName,
      })
    } catch (contextError: unknown) {
      if (contextError instanceof Error) {
        console.error(`${contextError.stack || contextError.message}`)
        const e = new GraphQLError(contextError.message, {
          originalError: contextError,
          nodes: documentAST,
        })
        // Return 400: Bad Request if any execution context errors exist.
        return c.json(errorMessages(['GraphQL execution context error.'], [e]), 400)
      }
      throw contextError
    }

    if (!result.data) {
      if (result.errors) {
        return c.json(errorMessages([result.errors.toString()], result.errors), 500)
      }
    }

    /*
    Now, does not support GraphiQL
    if (showGraphiQL) {
    }
    */

    if (pretty) {
      const payload = JSON.stringify(result, null, pretty ? 2 : 0)
      return c.text(payload, 200, {
        'Content-Type': 'application/json',
      })
    } else {
      return c.json(result)
    }

    await next() // XXX
  }
}