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 |
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 |
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 |
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 |
/**
* 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 |
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
}
}