Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "[HOTFIX] schema loader added to subscriptions",
"packageName": "@graphitation/supermassive",
"email": "77059398+vejrj@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ exports[`executeWithoutSchema - regression tests Executing mutation without type
}
`;

exports[`executeWithoutSchema - regression tests Executing subscription without type definitions but SchemaFragmentLoader will provide needed schema part 1`] = `
[
{
"data": {
"newFileEvent": {
"id": "file-1",
"name": "document.pdf",
},
},
},
{
"data": {
"newFileEvent": {
"id": "file-2",
"name": "image.png",
},
},
},
]
`;

exports[`executeWithoutSchema - regression tests Missing resolver should allow for partial data 1`] = `
{
"data": {
Expand All @@ -43,6 +64,22 @@ exports[`executeWithoutSchema - regression tests Missing type definition should
}
`;

exports[`executeWithoutSchema - regression tests Should fail when executing subscription without type definitions and schemaFragmentLoader still NOT returning requested schema part 1`] = `
{
"errors": [
[GraphQLError: Unexpected error value: "Type definition for Subscription.newFileEvent is missing"],
],
}
`;

exports[`executeWithoutSchema - regression tests Should fail while executing subscription without type definitions and without schema fragment loader 1`] = `
{
"errors": [
[GraphQLError: Unexpected error value: "Type definition for Subscription.newFileEvent is missing"],
],
}
`;

exports[`graphql-js snapshot check to ensure test stability Advanced Default value 1`] = `
{
"data": {
Expand Down
90 changes: 90 additions & 0 deletions packages/supermassive/src/__tests__/execute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,94 @@ describe("executeWithoutSchema - regression tests", () => {

expect(result).toMatchSnapshot();
});

test("Should fail while executing subscription without type definitions and without schema fragment loader", async () => {
const schemaFragment = {
schemaId: "test",
definitions: { types: {} },
resolvers: {},
};
const document = parse(
`subscription FetchFileEvent { newFileEvent { id } }`,
);

const result = await executeWithoutSchema({
document,
schemaFragment,
});

// Note: there used to be a discrepancy in the results based on whether schemaFramentLoader provided or not.
const result1 = await executeWithoutSchema({
document,
schemaFragment,
});

expect(result).toStrictEqual(result1);
expect(result).toMatchSnapshot();
});

test("Should fail when executing subscription without type definitions and schemaFragmentLoader still NOT returning requested schema part", async () => {
const schemaFragment = {
schemaId: "test",
definitions: { types: {} },
resolvers: {},
};
const document = parse(
`subscription FetchFileEvent { newFileEvent { id } }`,
);

const result = await executeWithoutSchema({
document,
schemaFragment,
});

// Note: there used to be a discrepancy in the results based on whether schemaFramentLoader provided or not.
const result1 = await executeWithoutSchema({
document,
schemaFragment,
schemaFragmentLoader: (currentFragment) =>
Promise.resolve({ mergedFragment: currentFragment }),
});

expect(result).toStrictEqual(result1);
expect(result).toMatchSnapshot();
});

test("Executing subscription without type definitions but SchemaFragmentLoader will provide needed schema part", async () => {
const fileEvents = [
{ newFileEvent: { id: "file-1", name: "document.pdf" } },
{ newFileEvent: { id: "file-2", name: "image.png" } },
];

const result = await executeWithoutSchema({
document: parse(
`subscription FetchFileEvent { newFileEvent { id name } }`,
),
schemaFragment: {
schemaId: "test",
definitions: { types: {} },
resolvers: {
Subscription: {
newFileEvent: {
subscribe: async function* () {
yield* fileEvents;
},
},
},
},
},
schemaFragmentLoader: (currentFragment) => {
currentFragment.definitions = encodeASTSchema(
parse(`
type Subscription { newFileEvent: File! }
type File { id: ID! name: String! }
`),
)[0];
return Promise.resolve({ mergedFragment: currentFragment });
},
});

const events = await drainExecution(result);
expect(events).toMatchSnapshot();
});
});
57 changes: 54 additions & 3 deletions packages/supermassive/src/executeWithoutSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ function afterFieldSubscribeHandle(
function executeSubscriptionImpl(
exeContext: ExecutionContext,
): PromiseOrValue<AsyncIterable<unknown>> {
const { operation, rootValue, schemaFragment } = exeContext;
const { operation, schemaFragment } = exeContext;
const rootTypeName = getOperationRootTypeName(operation);
const { groupedFieldSet } = collectFields(exeContext, rootTypeName);

Expand All @@ -718,13 +718,64 @@ function executeSubscriptionImpl(
fieldName,
);

if (!fieldDef) {
if (fieldDef) {
return runSubscriptionResolver(
exeContext,
fieldDef,
fieldName,
fieldGroup,
responseName,
rootTypeName,
);
}

const loading = requestSchemaFragment(exeContext, {
kind: "ReturnType",
parentTypeName: rootTypeName,
fieldName,
});

if (!loading) {
throw locatedError(
`The subscription field "${fieldName}" is not defined.`,
`Type definition for ${rootTypeName}.${fieldName} is missing`,
fieldGroup,
);
}

return loading.then(() => {
const fieldDef = Definitions.getField(
schemaFragment.definitions,
rootTypeName,
fieldName,
);

if (fieldDef === undefined) {
throw locatedError(
`Type definition for ${rootTypeName}.${fieldName} is missing`,
fieldGroup,
);
}

return runSubscriptionResolver(
exeContext,
fieldDef,
fieldName,
fieldGroup,
responseName,
rootTypeName,
);
});
}

function runSubscriptionResolver(
exeContext: ExecutionContext,
fieldDef: FieldDefinition,
fieldName: string,
fieldGroup: FieldGroup,
responseName: string,
rootTypeName: string,
): PromiseOrValue<AsyncIterable<unknown>> {
const { rootValue, schemaFragment } = exeContext;
const returnTypeRef = Definitions.getFieldTypeReference(fieldDef);
const resolveFn =
Resolvers.getSubscriptionFieldResolver(
Expand Down
Loading