import assert from 'node:assert/strict';
import { AsyncLocalStorage } from 'node:async_hooks';
import dc from 'node:diagnostics_channel';
import { buildSchema, execute, parse, subscribe, validate } from 'graphql';
function runParseCases() {
{
const events = [];
const handler = {
start: (msg) => events.push({ kind: 'start', source: msg.source }),
end: (msg) => events.push({ kind: 'end', source: msg.source }),
asyncStart: (msg) =>
events.push({ kind: 'asyncStart', source: msg.source }),
asyncEnd: (msg) => events.push({ kind: 'asyncEnd', source: msg.source }),
error: (msg) =>
events.push({ kind: 'error', source: msg.source, error: msg.error }),
};
const channel = dc.tracingChannel('graphql:parse');
channel.subscribe(handler);
try {
const doc = parse('{ field }');
assert.equal(doc.kind, 'Document');
assert.deepEqual(
events.map((e) => e.kind),
['start', 'end'],
);
assert.equal(events[0].source, '{ field }');
assert.equal(events[1].source, '{ field }');
} finally {
channel.unsubscribe(handler);
}
}
{
const events = [];
const handler = {
start: (msg) => events.push({ kind: 'start', source: msg.source }),
end: (msg) => events.push({ kind: 'end', source: msg.source }),
error: (msg) =>
events.push({ kind: 'error', source: msg.source, error: msg.error }),
};
const channel = dc.tracingChannel('graphql:parse');
channel.subscribe(handler);
try {
assert.throws(() => parse('{ '));
assert.deepEqual(
events.map((e) => e.kind),
['start', 'error', 'end'],
);
assert.ok(events[1].error instanceof Error);
} finally {
channel.unsubscribe(handler);
}
}
}
function runValidateCase() {
const schema = buildSchema(`type Query { field: String }`);
const doc = parse('{ field }');
const events = [];
const handler = {
start: (msg) =>
events.push({
kind: 'start',
schema: msg.schema,
document: msg.document,
}),
end: () => events.push({ kind: 'end' }),
error: (msg) => events.push({ kind: 'error', error: msg.error }),
};
const channel = dc.tracingChannel('graphql:validate');
channel.subscribe(handler);
try {
const errors = validate(schema, doc);
assert.deepEqual(errors, []);
assert.deepEqual(
events.map((e) => e.kind),
['start', 'end'],
);
assert.equal(events[0].schema, schema);
assert.equal(events[0].document, doc);
} finally {
channel.unsubscribe(handler);
}
}
function runExecuteCase() {
const schema = buildSchema(`type Query { hello: String }`);
const document = parse('query Greeting { hello }');
const events = [];
const handler = {
start: (msg) =>
events.push({
kind: 'start',
schema: msg.schema,
document: msg.document,
variableValues: msg.variableValues,
operationName: msg.operationName,
operationType: msg.operationType,
}),
end: () => events.push({ kind: 'end' }),
asyncStart: () => events.push({ kind: 'asyncStart' }),
asyncEnd: () => events.push({ kind: 'asyncEnd' }),
error: (msg) => events.push({ kind: 'error', error: msg.error }),
};
const channel = dc.tracingChannel('graphql:execute');
channel.subscribe(handler);
try {
const result = execute({
schema,
document,
rootValue: { hello: 'world' },
});
assert.equal(result.data.hello, 'world');
assert.deepEqual(
events.map((e) => e.kind),
['start', 'end'],
);
assert.equal(events[0].operationType, 'query');
assert.equal(events[0].operationName, 'Greeting');
assert.equal(events[0].document, document);
assert.equal(events[0].schema, schema);
} finally {
channel.unsubscribe(handler);
}
}
function runExecuteRootSelectionSetCase() {
const schema = buildSchema(`type Query { hello: String }`);
const document = parse('query Greeting { hello }');
const operation = document.definitions[0];
const events = [];
const handler = {
start: (msg) =>
events.push({
kind: 'start',
schema: msg.schema,
document: msg.document,
operation: msg.operation,
variableValues: msg.variableValues,
operationName: msg.operationName,
operationType: msg.operationType,
}),
end: (msg) => events.push({ kind: 'end', result: msg.result }),
asyncStart: () => events.push({ kind: 'asyncStart' }),
asyncEnd: () => events.push({ kind: 'asyncEnd' }),
error: (msg) => events.push({ kind: 'error', error: msg.error }),
};
const channel = dc.tracingChannel('graphql:execute:rootSelectionSet');
channel.subscribe(handler);
try {
const result = execute({
schema,
document,
rootValue: { hello: 'world' },
});
assert.equal(result.data.hello, 'world');
assert.deepEqual(
events.map((e) => e.kind),
['start', 'end'],
);
assert.equal(events[0].operationType, 'query');
assert.equal(events[0].operationName, 'Greeting');
assert.equal(events[0].document, document);
assert.equal(events[0].operation, operation);
assert.equal(events[0].schema, schema);
assert.equal(events[1].result, result);
} finally {
channel.unsubscribe(handler);
}
}
async function runSubscribeCase() {
async function* ticks() {
yield { tick: 'one' };
}
const schema = buildSchema(`
type Query { dummy: String }
type Subscription { tick: String }
`);
schema.getSubscriptionType().getFields().tick.subscribe = () => ticks();
const document = parse('subscription Tick { tick }');
const events = [];
const handler = {
start: (msg) =>
events.push({
kind: 'start',
schema: msg.schema,
document: msg.document,
variableValues: msg.variableValues,
operationName: msg.operationName,
operationType: msg.operationType,
}),
end: () => events.push({ kind: 'end' }),
asyncStart: () => events.push({ kind: 'asyncStart' }),
asyncEnd: () => events.push({ kind: 'asyncEnd' }),
error: (msg) => events.push({ kind: 'error', error: msg.error }),
};
const channel = dc.tracingChannel('graphql:subscribe');
channel.subscribe(handler);
try {
const result = subscribe({ schema, document });
const stream = typeof result.then === 'function' ? await result : result;
if (stream[Symbol.asyncIterator]) {
await stream.return?.();
}
assert.deepEqual(
events.map((e) => e.kind),
['start', 'end'],
);
assert.equal(events[0].operationType, 'subscription');
assert.equal(events[0].operationName, 'Tick');
} finally {
channel.unsubscribe(handler);
}
}
function runResolveCase() {
const schema = buildSchema(
`type Query { hello: String nested: Nested } type Nested { leaf: String }`,
);
const document = parse('{ hello nested { leaf } }');
const events = [];
const handler = {
start: (msg) =>
events.push({
kind: 'start',
fieldName: msg.fieldName,
parentType: msg.parentType,
fieldType: msg.fieldType,
args: msg.args,
isDefaultResolver: msg.isDefaultResolver,
fieldPath: msg.fieldPath,
}),
end: () => events.push({ kind: 'end' }),
asyncStart: () => events.push({ kind: 'asyncStart' }),
asyncEnd: () => events.push({ kind: 'asyncEnd' }),
error: (msg) => events.push({ kind: 'error', error: msg.error }),
};
const channel = dc.tracingChannel('graphql:resolve');
channel.subscribe(handler);
try {
const rootValue = { hello: () => 'world', nested: { leaf: 'leaf-value' } };
execute({ schema, document, rootValue });
const starts = events.filter((e) => e.kind === 'start');
const paths = starts.map((e) => e.fieldPath);
assert.deepEqual(paths, ['hello', 'nested', 'nested.leaf']);
const hello = starts.find((e) => e.fieldName === 'hello');
assert.equal(hello.parentType, 'Query');
assert.equal(hello.fieldType, 'String');
assert.equal(hello.isDefaultResolver, true);
} finally {
channel.unsubscribe(handler);
}
}
function runNoSubscriberCase() {
const doc = parse('{ field }');
assert.equal(doc.kind, 'Document');
}
async function runAlsPropagationCase() {
const als = new AsyncLocalStorage();
const channel = dc.tracingChannel('graphql:execute');
channel.start.bindStore(als, (context) => ({
operationName: context.operationName,
}));
const seen = {};
const handler = {
start: () => (seen.start = als.getStore()),
end: () => (seen.end = als.getStore()),
asyncStart: () => (seen.asyncStart = als.getStore()),
asyncEnd: () => (seen.asyncEnd = als.getStore()),
};
channel.subscribe(handler);
try {
const schema = buildSchema(`type Query { slow: String }`);
const document = parse('query Slow { slow }');
const rootValue = { slow: () => Promise.resolve('done') };
await execute({ schema, document, rootValue });
assert.deepEqual(seen.start, { operationName: 'Slow' });
assert.deepEqual(seen.end, { operationName: 'Slow' });
assert.deepEqual(seen.asyncStart, { operationName: 'Slow' });
assert.deepEqual(seen.asyncEnd, { operationName: 'Slow' });
} finally {
channel.unsubscribe(handler);
channel.start.unbindStore(als);
}
}
async function main() {
runParseCases();
runValidateCase();
runExecuteCase();
runExecuteRootSelectionSetCase();
await runSubscribeCase();
runResolveCase();
await runAlsPropagationCase();
runNoSubscriberCase();
console.log('diagnostics integration test passed');
}
main();