From 75c33b3aa2dac2e6d295da8c554bde13de904112 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Sun, 15 Dec 2024 15:58:47 +0100 Subject: [PATCH 01/21] adding realese config --- .azure-devops/graphitation-release.yml | 1 + package.json | 2 +- packages/supermassive/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.azure-devops/graphitation-release.yml b/.azure-devops/graphitation-release.yml index e5ff2a284..c54cd8f8d 100644 --- a/.azure-devops/graphitation-release.yml +++ b/.azure-devops/graphitation-release.yml @@ -2,6 +2,7 @@ pr: none trigger: - main - alloy/relay-apollo-duct-tape + - jvejr/supermassive-hooks-error-handling-alpha variables: - group: InfoSec-SecurityResults diff --git a/package.json b/package.json index 6d0c7990b..73075bb44 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "beachball": "beachball -b origin/main", "change": "yarn beachball change", "checkchange": "yarn beachball check", - "release": "yarn beachball publish -t latest", + "release": "yarn beachball publish -t alpha", "postinstall": "patch-package" }, "devDependencies": { diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index 936979128..933ad4716 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.7.2", + "version": "3.8.0-alpha.0", "main": "./src/index.ts", "repository": { "type": "git", From db44158751f1676abc1fcd1eba184f9cfc723d6f Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Sun, 15 Dec 2024 16:10:29 +0100 Subject: [PATCH 02/21] fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73075bb44..57645c2b6 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint": "lage lint --continue", "lage": "lage", "ci": "yarn lage build types test lint && yarn checkchange", - "beachball": "beachball -b origin/main", + "beachball": "beachball -b origin/jvejr/supermassive-hooks-error-handling-alpha", "change": "yarn beachball change", "checkchange": "yarn beachball check", "release": "yarn beachball publish -t alpha", From 67b5597be4d2e3bd88449bef1ed4aa849348e089 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:53:00 +0100 Subject: [PATCH 03/21] [WIP] hooks error handling (#490) * Supermassive hook error handling modified --- ...-35e31e43-c61d-4e52-a933-e51e4a1aad72.json | 7 + .../supermassive/src/__tests__/hooks.test.ts | 566 +++++++++++++++--- .../supermassive/src/executeWithoutSchema.ts | 451 ++++++++++---- packages/supermassive/src/hooks/types.ts | 62 +- 4 files changed, 853 insertions(+), 233 deletions(-) create mode 100644 change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json diff --git a/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json b/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json new file mode 100644 index 000000000..5d757058d --- /dev/null +++ b/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Supermassive hook error handling modified", + "packageName": "@graphitation/supermassive", + "email": "77059398+vejrj@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/packages/supermassive/src/__tests__/hooks.test.ts b/packages/supermassive/src/__tests__/hooks.test.ts index 155f43b47..77471e53b 100644 --- a/packages/supermassive/src/__tests__/hooks.test.ts +++ b/packages/supermassive/src/__tests__/hooks.test.ts @@ -17,6 +17,7 @@ import type { UserResolvers, TotalExecutionResult } from "../types"; import type { AfterFieldCompleteHookArgs, AfterFieldResolveHookArgs, + AfterFieldSubscribeHookArgs, BaseExecuteFieldHookArgs, BaseExecuteOperationHookArgs, BeforeSubscriptionEventEmitHookArgs, @@ -136,6 +137,26 @@ describe.each([ ); }, ), + afterFieldSubscribe: jest + .fn() + .mockImplementation( + ({ + resolveInfo, + result, + error, + }: AfterFieldSubscribeHookArgs) => { + const resultValue = + typeof result === "object" && result !== null + ? "[object]" + : result; + const errorMessage = error instanceof Error ? error.message : error; + hookCalls.push( + `AFS|${pathToArray(resolveInfo.path).join( + ".", + )}|${resultValue}|${errorMessage}`, + ); + }, + ), afterFieldComplete: jest .fn() .mockImplementation( @@ -187,6 +208,13 @@ describe.each([ hookCalls.push(`BFR|${pathToArray(resolveInfo.path).join(".")}`); }, ), + beforeFieldSubscribe: jest + .fn() + .mockImplementation( + ({ resolveInfo }: BaseExecuteFieldHookArgs) => { + hookCalls.push(`BFS|${pathToArray(resolveInfo.path).join(".")}`); + }, + ), }; const asyncBeforeHooks: ExecutionHooks = { @@ -194,7 +222,7 @@ describe.each([ .fn() .mockImplementation( async ({ operation }: BaseExecuteOperationHookArgs) => { - hookCalls.push(`BOE|${operation.name?.value}`); + hookCalls.push(`ABOE|${operation.name?.value}`); }, ), beforeSubscriptionEventEmit: jest @@ -205,7 +233,7 @@ describe.each([ eventPayload, }: BeforeSubscriptionEventEmitHookArgs) => { hookCalls.push( - `BSE|${operation.name?.value}|${ + `ABSE|${operation.name?.value}|${ (eventPayload as any).emitPersons.name }`, ); @@ -215,7 +243,14 @@ describe.each([ .fn() .mockImplementation( async ({ resolveInfo }: BaseExecuteFieldHookArgs) => { - hookCalls.push(`BFR|${pathToArray(resolveInfo.path).join(".")}`); + hookCalls.push(`ABFR|${pathToArray(resolveInfo.path).join(".")}`); + }, + ), + beforeFieldSubscribe: jest + .fn() + .mockImplementation( + async ({ resolveInfo }: BaseExecuteFieldHookArgs) => { + hookCalls.push(`ABFR|${pathToArray(resolveInfo.path).join(".")}`); }, ), }; @@ -516,8 +551,8 @@ describe.each([ resolvers: resolvers as UserResolvers, expectedHookCalls: [ "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|[object]|undefined", + "BFS|emitPersons", + "AFS|emitPersons|[object]|undefined", "BSE|EmitPersons|Luke Skywalker", "ABR|EmitPersons", "BSE|EmitPersons|C-3PO", @@ -551,8 +586,8 @@ describe.each([ }, expectedHookCalls: [ "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|undefined|Subscribe error", + "BFS|emitPersons", + "AFS|emitPersons|undefined|Subscribe error", ], resultHasErrors: true, isStrictHookCallsOrder: true, @@ -584,8 +619,8 @@ describe.each([ }, expectedHookCalls: [ "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|undefined|Subscribe error", + "BFS|emitPersons", + "AFS|emitPersons|undefined|Subscribe error", ], resultHasErrors: true, isStrictHookCallsOrder: true, @@ -610,10 +645,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetPerson", - "BFR|person", + "ABOE|GetPerson", + "ABFR|person", "AFR|person|[object]|undefined", - "BFR|person.name", + "ABFR|person.name", "AFR|person.name|Luke Skywalker|undefined", "AFC|person.name|Luke Skywalker|undefined", "AFC|person|[object]|undefined", @@ -639,10 +674,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetPerson", - "BFR|person", + "ABOE|GetPerson", + "ABFR|person", "AFR|person|[object]|undefined", - "BFR|person.name", + "ABFR|person.name", "AFR|person.name|Luke Skywalker|undefined", "AFC|person.name|Luke Skywalker|undefined", "AFC|person|[object]|undefined", @@ -668,10 +703,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", - "BFR|film.producer", + "ABFR|film.producer", "AFR|film.producer|undefined|Resolver error", "AFC|film.producer|undefined|Resolver error", "AFC|film|[object]|undefined", @@ -697,10 +732,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", - "BFR|film.producer", + "ABFR|film.producer", "AFR|film.producer|undefined|Resolver error", "AFC|film.producer|undefined|Resolver error", "AFC|film|[object]|undefined", @@ -726,10 +761,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", - "BFR|film.title", + "ABFR|film.title", "AFR|film.title|undefined|Resolver error", "AFC|film.title|undefined|Resolver error", "AFC|film|undefined|Resolver error", @@ -755,10 +790,10 @@ describe.each([ }, } as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", - "BFR|film.title", + "ABFR|film.title", "AFR|film.title|undefined|Resolver error", "AFC|film.title|undefined|Resolver error", "AFC|film|undefined|Resolver error", @@ -777,8 +812,8 @@ describe.each([ }`, resolvers: resolvers as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", "AFC|film|[object]|undefined", "ABR|GetFilm", @@ -797,8 +832,8 @@ describe.each([ }`, resolvers: resolvers as UserResolvers, expectedHookCalls: [ - "BOE|GetFilm", - "BFR|film", + "ABOE|GetFilm", + "ABFR|film", "AFR|film|[object]|undefined", "AFC|film|[object]|undefined", "ABR|GetFilm", @@ -819,9 +854,9 @@ describe.each([ }`, resolvers: resolvers as UserResolvers, expectedHookCalls: [ - "BOE|GetFilmAndPerson", - "BFR|film", - "BFR|person", + "ABOE|GetFilmAndPerson", + "ABFR|film", + "ABFR|person", "AFR|film|[object]|undefined", "AFR|person|[object]|undefined", "AFC|film|[object]|undefined", @@ -844,14 +879,14 @@ describe.each([ }, resolvers: resolvers as UserResolvers, expectedHookCalls: [ - "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|[object]|undefined", - "BSE|EmitPersons|Luke Skywalker", + "ABOE|EmitPersons", + "ABFR|emitPersons", + "AFS|emitPersons|[object]|undefined", + "ABSE|EmitPersons|Luke Skywalker", "ABR|EmitPersons", - "BSE|EmitPersons|C-3PO", + "ABSE|EmitPersons|C-3PO", "ABR|EmitPersons", - "BSE|EmitPersons|R2-D2", + "ABSE|EmitPersons|R2-D2", "ABR|EmitPersons", ], resultHasErrors: false, @@ -879,9 +914,9 @@ describe.each([ limit: 1, }, expectedHookCalls: [ - "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|undefined|Subscribe error", + "ABOE|EmitPersons", + "ABFR|emitPersons", + "AFS|emitPersons|undefined|Subscribe error", ], resultHasErrors: true, isStrictHookCallsOrder: true, @@ -912,9 +947,9 @@ describe.each([ limit: 1, }, expectedHookCalls: [ - "BOE|EmitPersons", - "BFR|emitPersons", - "AFR|emitPersons|undefined|Subscribe error", + "ABOE|EmitPersons", + "ABFR|emitPersons", + "AFS|emitPersons|undefined|Subscribe error", ], resultHasErrors: true, isStrictHookCallsOrder: true, @@ -1019,11 +1054,15 @@ describe.each([ .fn() .mockImplementation( ({ resolveInfo }: BaseExecuteFieldHookArgs) => { + if (resolveInfo.fieldName === "film") { + hookCalls.push( + `ABFR|${pathToArray(resolveInfo.path).join(".")}`, + ); + return Promise.resolve(); + } hookCalls.push( `BFR|${pathToArray(resolveInfo.path).join(".")}`, ); - if (resolveInfo.fieldName === "film") - return Promise.resolve(); return; }, ), @@ -1035,8 +1074,8 @@ describe.each([ ); const expectedHookCalls = [ - "BOE|GetFilmAndPerson", - "BFR|film", + "ABOE|GetFilmAndPerson", + "ABFR|film", "BFR|person", "AFR|person|[object]|undefined", "AFC|person|[object]|undefined", @@ -1054,7 +1093,229 @@ describe.each([ }); }); - describe("Error thrown in the hook doesn't break execution and is returned in response 'errors'", () => { + describe("error in subscription is thrown", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases: Array = [ + { + name: "beforeSubscriptionEventEmit (Error is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + beforeSubscriptionEventEmit: jest.fn().mockImplementation(() => { + throw new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + }, + { + name: "async beforeSubscriptionEventEmit (Error is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + beforeSubscriptionEventEmit: jest + .fn() + .mockImplementation(async () => { + throw new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + }, + ]; + + it.each(testCases)( + "$name", + async ({ document, hooks, expectedErrorMessage, variables }) => { + expect.assertions(5); + const parsedDocument = parse(document); + + const response = await drainExecution( + await execute( + parsedDocument, + resolvers as UserResolvers, + hooks, + variables, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + + expect(isTotalExecutionResult(result)).toBe(true); + + const errors = result.errors; + expect(result.data).toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe(expectedErrorMessage); + }, + ); + }); + + describe("Error in subscription during creating event stream", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases: Array = [ + { + name: "beforeFieldSubscribe (Error is returned)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + beforeFieldSubscribe: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in beforeFieldSubscribe hook: Hook error", + }, + { + name: "afterFieldSubscribe (Error is returned)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + afterFieldSubscribe: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in afterFieldSubscribe hook: Hook error", + }, + { + name: "beforeFieldSubscribe (Error is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + beforeFieldSubscribe: jest.fn().mockImplementation(() => { + throw new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in beforeFieldSubscribe hook: Hook error", + }, + { + name: "beforeFieldSubscribe (string is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + beforeFieldSubscribe: jest.fn().mockImplementation(() => { + throw "Hook error"; + }), + }, + expectedErrorMessage: + 'Unexpected error in beforeFieldSubscribe hook: "Hook error"', + }, + { + name: "afterFieldSubscribe (Error is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + afterFieldSubscribe: jest.fn().mockImplementation(() => { + throw new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in afterFieldSubscribe hook: Hook error", + }, + { + name: "afterFieldSubscribe (string is thrown)", + document: `subscription EmitPersons($limit: Int!) + { + emitPersons(limit: $limit) { + name + } + }`, + variables: { + limit: 1, + }, + hooks: { + afterFieldSubscribe: jest.fn().mockImplementation(() => { + throw "Hook error"; + }), + }, + expectedErrorMessage: + 'Unexpected error in afterFieldSubscribe hook: "Hook error"', + }, + ]; + + it.each(testCases)( + "$name", + async ({ document, hooks, expectedErrorMessage, variables }) => { + expect.assertions(5); + const parsedDocument = parse(document); + + const response = await drainExecution( + await execute( + parsedDocument, + resolvers as UserResolvers, + hooks, + variables, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + expect(result.data).toBeUndefined(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe(expectedErrorMessage); + }, + ); + }); + + describe("Error is thrown by hooks during field resolution", () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -1156,8 +1417,43 @@ describe.each([ expectedErrorMessage: 'Unexpected error in afterFieldComplete hook: "Hook error"', }, + ]; + + it.each(testCases)( + "$name", + async ({ document, hooks, expectedErrorMessage, variables }) => { + expect.assertions(5); + const parsedDocument = parse(document); + + const response = await drainExecution( + await execute( + parsedDocument, + resolvers as UserResolvers, + hooks, + variables, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + + expect(result.data.film).toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe(expectedErrorMessage); + }, + ); + }); + + describe("Error is returned by hooks during field resolution", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases: Array = [ { - name: "beforeOperationExecute (Error is thrown)", + name: "beforeFieldResolve (Error is returned)", document: ` { film(id: 1) { @@ -1165,15 +1461,15 @@ describe.each([ } }`, hooks: { - beforeOperationExecute: jest.fn().mockImplementation(() => { - throw new Error("Hook error"); + beforeFieldResolve: jest.fn().mockImplementation(() => { + return new Error("Hook error"); }), }, expectedErrorMessage: - "Unexpected error in beforeOperationExecute hook: Hook error", + "Unexpected error in beforeFieldResolve hook: Hook error", }, { - name: "beforeOperationExecute (string is thrown)", + name: "afterFieldResolve (Error is returned)", document: ` { film(id: 1) { @@ -1181,15 +1477,15 @@ describe.each([ } }`, hooks: { - beforeOperationExecute: jest.fn().mockImplementation(() => { - throw "Hook error"; + afterFieldResolve: jest.fn().mockImplementation(() => { + return new Error("Hook error"); }), }, expectedErrorMessage: - 'Unexpected error in beforeOperationExecute hook: "Hook error"', + "Unexpected error in afterFieldResolve hook: Hook error", }, { - name: "afterBuildResponse (Error is thrown)", + name: "afterFieldComplete (Error is returned)", document: ` { film(id: 1) { @@ -1197,15 +1493,64 @@ describe.each([ } }`, hooks: { - afterBuildResponse: jest.fn().mockImplementation(() => { + afterFieldComplete: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in afterFieldComplete hook: Hook error", + }, + ]; + + it.each(testCases)( + "$name", + async ({ document, hooks, expectedErrorMessage, variables }) => { + expect.assertions(5); + const parsedDocument = parse(document); + + const response = await drainExecution( + await execute( + parsedDocument, + resolvers as UserResolvers, + hooks, + variables, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + expect(result.data.film).not.toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe(expectedErrorMessage); + }, + ); + }); + + describe("Error thrown in the BEFORE OPERATION hook breaks execution", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases: Array = [ + { + name: "beforeOperationExecute (Error is thrown)", + document: ` + { + film(id: 1) { + title + } + }`, + hooks: { + beforeOperationExecute: jest.fn().mockImplementation(() => { throw new Error("Hook error"); }), }, expectedErrorMessage: - "Unexpected error in afterBuildResponse hook: Hook error", + "Unexpected error in beforeOperationExecute hook: Hook error", }, { - name: "afterBuildResponse (string is thrown)", + name: "beforeOperationExecute (string is thrown)", document: ` { film(id: 1) { @@ -1213,53 +1558,90 @@ describe.each([ } }`, hooks: { - afterBuildResponse: jest.fn().mockImplementation(() => { + beforeOperationExecute: jest.fn().mockImplementation(() => { throw "Hook error"; }), }, expectedErrorMessage: - 'Unexpected error in afterBuildResponse hook: "Hook error"', + 'Unexpected error in beforeOperationExecute hook: "Hook error"', }, + ]; + + it.each(testCases)( + "$name", + async ({ document, hooks, expectedErrorMessage, variables }) => { + expect.assertions(1); + const parsedDocument = parse(document); + + await expect(async function () { + const result = await execute( + parsedDocument, + resolvers as UserResolvers, + hooks, + variables, + ); + return drainExecution(result); + }).rejects.toThrow(expectedErrorMessage); + }, + ); + }); + + describe("Error returned in the hook doesn't break execution and is returned in response 'errors'", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const testCases: Array = [ { - name: "beforeSubscriptionEventEmit (Error is thrown)", - document: `subscription EmitPersons($limit: Int!) + name: "beforeFieldResolve (Error is thrown)", + document: ` { - emitPersons(limit: $limit) { - name + film(id: 1) { + title } }`, - variables: { - limit: 1, - }, hooks: { - beforeSubscriptionEventEmit: jest.fn().mockImplementation(() => { - throw new Error("Hook error"); + beforeFieldResolve: jest.fn().mockImplementation(() => { + return new Error("Hook error"); }), }, expectedErrorMessage: - "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + "Unexpected error in beforeFieldResolve hook: Hook error", }, { - name: "beforeSubscriptionEventEmit (string is thrown)", - document: `subscription EmitPersons($limit: Int!) + name: "afterFieldResolve (Error is thrown)", + document: ` { - emitPersons(limit: $limit) { - name + film(id: 1) { + title } }`, - variables: { - limit: 1, + hooks: { + afterFieldResolve: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), }, + expectedErrorMessage: + "Unexpected error in afterFieldResolve hook: Hook error", + }, + { + name: "afterFieldComplete (Error is thrown)", + document: ` + { + film(id: 1) { + title + } + }`, hooks: { - beforeSubscriptionEventEmit: jest.fn().mockImplementation(() => { - throw "Hook error"; + afterFieldComplete: jest.fn().mockImplementation(() => { + return new Error("Hook error"); }), }, expectedErrorMessage: - 'Unexpected error in beforeSubscriptionEventEmit hook: "Hook error"', + "Unexpected error in afterFieldComplete hook: Hook error", }, { - name: "async beforeSubscriptionEventEmit (Error is thrown)", + name: "beforeSubscriptionEventEmit (Error is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1270,17 +1652,15 @@ describe.each([ limit: 1, }, hooks: { - beforeSubscriptionEventEmit: jest - .fn() - .mockImplementation(async () => { - throw new Error("Hook error"); - }), + beforeSubscriptionEventEmit: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), }, expectedErrorMessage: "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", }, { - name: "async beforeSubscriptionEventEmit (string is thrown)", + name: "async beforeSubscriptionEventEmit (Error is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1294,11 +1674,11 @@ describe.each([ beforeSubscriptionEventEmit: jest .fn() .mockImplementation(async () => { - throw "Hook error"; + return new Error("Hook error"); }), }, expectedErrorMessage: - 'Unexpected error in beforeSubscriptionEventEmit hook: "Hook error"', + "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", }, ]; diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index c5807b3dd..46f20a920 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -281,7 +281,7 @@ function executeOperationWithBeforeHook( exeContext: ExecutionContext, ): PromiseOrValue { const hooks = exeContext.fieldExecutionHooks; - let hook: Promise | void | undefined; + let hook; if (hooks?.beforeOperationExecute) { hook = invokeBeforeOperationExecuteHook(exeContext); } @@ -702,60 +702,84 @@ function executeSubscriptionImpl( // used to represent an authenticated user, or request-specific caches. const contextValue = exeContext.contextValue; - if (!isDefaultResolverUsed && hooks?.beforeFieldResolve) { - hookContext = invokeBeforeFieldResolveHook(info, exeContext); + if (!isDefaultResolverUsed && hooks?.beforeFieldSubscribe) { + hookContext = invokeBeforeFieldSubscribeHook(info, exeContext); } - // Call the `subscribe()` resolver or the default resolver to produce an - // AsyncIterable yielding raw payloads. - const result = isPromise(hookContext) - ? hookContext.then((context) => { + let result: unknown; + + if (hookContext) { + if (isPromise(hookContext)) { + result = hookContext.then((context) => { hookContext = context; + + if (hookContext instanceof GraphQLError) { + return null; + } + return resolveFn(rootValue, args, contextValue, info); - }) - : resolveFn(rootValue, args, contextValue, info); + }); + } else if (hookContext instanceof GraphQLError) { + result = null; + } + } + // Call the `subscribe()` resolver or the default resolver to produce an + // AsyncIterable yielding raw payloads. + if (result === undefined) { + result = resolveFn(rootValue, args, contextValue, info); + } + + const afterFieldSubscribeHandle = (resolved: unknown, error?: Error) => { + if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { + hookContext = invokeAfterFieldSubscribeHook( + info, + exeContext, + hookContext, + resolved, + error, + ); + + if (hookContext instanceof GraphQLError) { + throw hookContext; + } + } + }; if (isPromise(result)) { return result.then(assertEventStream).then( (resolved) => { - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( + if (resolved instanceof GraphQLError) { + throw resolved; + } + + if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { + hookContext = invokeAfterFieldSubscribeHook( info, exeContext, hookContext, resolved, ); + + if (hookContext instanceof GraphQLError) { + throw hookContext; + } } return resolved; }, (error) => { - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( - info, - exeContext, - hookContext, - undefined, - error, - ); - } + afterFieldSubscribeHandle(undefined, error); + throw locatedError(error, fieldGroup, pathToArray(path)); }, ); } const stream = assertEventStream(result); - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( - info, - exeContext, - hookContext, - stream, - ); - } + afterFieldSubscribeHandle(stream); return stream; } catch (error) { - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( + if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { + hookContext = invokeAfterFieldSubscribeHook( info, exeContext, hookContext, @@ -763,6 +787,11 @@ function executeSubscriptionImpl( error, ); } + + if (hookContext instanceof GraphQLError) { + throw hookContext; + } + throw locatedError(error, fieldGroup, pathToArray(path)); } } @@ -823,27 +852,34 @@ function mapResultOrEventStreamOrPromise( payload, ); const hooks = exeContext?.fieldExecutionHooks; - let beforeExecuteFieldsHook: void | Promise | undefined; + let beforeExecuteSubscriptionEvenEmitHook; + if (hooks?.beforeSubscriptionEventEmit) { - beforeExecuteFieldsHook = invokeBeforeSubscriptionEventEmitHook( - perEventContext, - payload, - ); + beforeExecuteSubscriptionEvenEmitHook = + invokeBeforeSubscriptionEventEmitHook(perEventContext, payload); + + if (beforeExecuteSubscriptionEvenEmitHook instanceof GraphQLError) { + return buildResponse(perEventContext, null) as TotalExecutionResult; + } } try { - const data = isPromise(beforeExecuteFieldsHook) - ? beforeExecuteFieldsHook.then(() => - executeFields( - exeContext, + const data = isPromise(beforeExecuteSubscriptionEvenEmitHook) + ? beforeExecuteSubscriptionEvenEmitHook.then((context) => { + if (context instanceof GraphQLError) { + return null; + } + + return executeFields( + perEventContext, parentTypeName, payload, path, groupedFieldSet, undefined, - ), - ) + ); + }) : executeFields( - exeContext, + perEventContext, parentTypeName, payload, path, @@ -961,60 +997,78 @@ function resolveAndCompleteField( hookContext = invokeBeforeFieldResolveHook(info, exeContext); } - const result = isPromise(hookContext) - ? hookContext.then((context) => { - hookContext = context; - return resolveFn(source, args, contextValue, info); - }) - : resolveFn(source, args, contextValue, info); + let result: unknown; + + if (hookContext instanceof GraphQLError) { + result = null; + } else if (isPromise(hookContext)) { + result = hookContext.then((context) => { + hookContext = context; + + if (hookContext instanceof GraphQLError) { + return null; + } + + return resolveFn(source, args, contextValue, info); + }); + } else { + result = resolveFn(source, args, contextValue, info); + } + let completed; + const handleAfterFieldHooks = + ( + hook: + | typeof invokeAfterFieldResolveHook + | typeof invokeAfterFieldCompleteHook, + useHook: boolean, + ) => + (resolved: unknown, error?: Error) => { + if (!isDefaultResolverUsed && useHook) { + hookContext = hook(info, exeContext, hookContext, resolved, error); + return hookContext instanceof GraphQLError ? null : resolved; + } + + return resolved; + }; + if (isPromise(result)) { - completed = result.then( - (resolved) => { - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( - info, + completed = result + .then( + handleAfterFieldHooks( + invokeAfterFieldResolveHook, + !!hooks?.afterFieldResolve, + ), + ) + .then( + (resolved) => { + return completeValue( exeContext, - hookContext, - resolved, - ); - } - return completeValue( - exeContext, - returnTypeRef, - fieldGroup, - info, - path, - resolved, - incrementalDataRecord, - ); - }, - (rawError) => { - // That's where afterResolve hook can only be called - // in the case of async resolver promise rejection. - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( + returnTypeRef, + fieldGroup, info, - exeContext, - hookContext, - undefined, - rawError, + path, + hookContext instanceof GraphQLError ? null : resolved, + incrementalDataRecord, ); - } - // Error will be handled on field completion - throw rawError; - }, - ); - } else { - if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { - hookContext = invokeAfterFieldResolveHook( - info, - exeContext, - hookContext, - result, + }, + (rawError) => { + // That's where afterResolve hook can only be called + // in the case of async resolver promise rejection. + handleAfterFieldHooks( + invokeAfterFieldResolveHook, + !!hooks?.afterFieldResolve, + )(undefined, rawError); + // Error will be handled on field completion + throw rawError; + }, ); - } + } else { + result = handleAfterFieldHooks( + invokeAfterFieldResolveHook, + !!hooks?.afterFieldResolve, + )(result); completed = completeValue( exeContext, returnTypeRef, @@ -1030,28 +1084,22 @@ function resolveAndCompleteField( // Note: we don't rely on a `catch` method, but we do expect "thenable" // to take a second callback for the error case. return completed.then( - (resolved) => { - if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { - invokeAfterFieldCompleteHook( - info, - exeContext, - hookContext, - resolved, - ); - } - return resolved; - }, + handleAfterFieldHooks( + invokeAfterFieldCompleteHook, + !!hooks?.afterFieldComplete, + ), (rawError) => { const error = locatedError(rawError, fieldGroup, pathToArray(path)); - if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { - invokeAfterFieldCompleteHook( - info, - exeContext, - hookContext, - undefined, - error, - ); + + const hookResult = handleAfterFieldHooks( + invokeAfterFieldCompleteHook, + !!hooks?.afterFieldComplete, + )(undefined, error); + + if (hookResult === null) { + return null; } + handleFieldError( rawError, exeContext, @@ -1064,10 +1112,11 @@ function resolveAndCompleteField( }, ); } - if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { - invokeAfterFieldCompleteHook(info, exeContext, hookContext, completed); - } - return completed; + + return handleAfterFieldHooks( + invokeAfterFieldCompleteHook, + !!hooks?.afterFieldComplete, + )(completed); } catch (rawError) { const pathArray = pathToArray(path); const error = locatedError(rawError, fieldGroup, pathArray); @@ -1090,14 +1139,19 @@ function resolveAndCompleteField( ); } if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { - invokeAfterFieldCompleteHook( + const invokeAfterFieldCompleteHookResult = invokeAfterFieldCompleteHook( info, exeContext, hookContext, undefined, error, ); + + if (invokeAfterFieldCompleteHookResult instanceof GraphQLError) { + return null; + } } + handleFieldError( rawError, exeContext, @@ -1817,6 +1871,47 @@ function collectAndExecuteSubfields( return subFields; } +function invokeBeforeFieldSubscribeHook( + resolveInfo: ResolveInfo, + exeContext: ExecutionContext, +) { + const hook = exeContext.fieldExecutionHooks?.beforeFieldSubscribe; + if (!hook) { + return; + } + + return executeSafe( + () => + hook({ + resolveInfo, + context: exeContext.contextValue, + }), + (result, rawError) => { + if (rawError) { + const error = toGraphQLError( + rawError, + resolveInfo.path, + "Unexpected error in beforeFieldSubscribe hook", + ); + exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + resolveInfo.path, + "Unexpected error in beforeFieldSubscribe hook", + ); + exeContext.errors.push(error); + + return error; + } + + return result; + }, + ); +} + function invokeBeforeFieldResolveHook( resolveInfo: ResolveInfo, exeContext: ExecutionContext, @@ -1825,13 +1920,14 @@ function invokeBeforeFieldResolveHook( if (!hook) { return; } + return executeSafe( () => hook({ resolveInfo, context: exeContext.contextValue, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { const error = toGraphQLError( rawError, @@ -1839,7 +1935,18 @@ function invokeBeforeFieldResolveHook( "Unexpected error in beforeFieldResolve hook", ); exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + resolveInfo.path, + "Unexpected error in beforeFieldResolve hook", + ); + exeContext.errors.push(error); } + + return result; }, ); } @@ -1864,7 +1971,7 @@ function invokeAfterFieldResolveHook( result, error, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { const error = toGraphQLError( rawError, @@ -1872,7 +1979,64 @@ function invokeAfterFieldResolveHook( "Unexpected error in afterFieldResolve hook", ); exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + resolveInfo.path, + "Unexpected error in afterFieldResolve hook", + ); + exeContext.errors.push(error); } + + return result; + }, + ); +} + +function invokeAfterFieldSubscribeHook( + resolveInfo: ResolveInfo, + exeContext: ExecutionContext, + hookContext: unknown, + result?: unknown, + error?: unknown, +) { + const hook = exeContext.fieldExecutionHooks?.afterFieldSubscribe; + if (!hook) { + return; + } + return executeSafe( + () => + hook({ + resolveInfo, + context: exeContext.contextValue, + hookContext, + result, + error, + }), + (result, rawError) => { + if (rawError) { + const error = toGraphQLError( + rawError, + resolveInfo.path, + "Unexpected error in afterFieldSubscribe hook", + ); + exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + resolveInfo.path, + "Unexpected error in afterFieldSubscribe hook", + ); + exeContext.errors.push(error); + + return error; + } + + return result; }, ); } @@ -1883,12 +2047,12 @@ function invokeAfterFieldCompleteHook( hookContext: unknown, result?: unknown, error?: unknown, -): void { +) { const hook = exeContext.fieldExecutionHooks?.afterFieldComplete; if (!hook) { return; } - executeSafe( + return executeSafe( () => hook({ resolveInfo, @@ -1897,7 +2061,7 @@ function invokeAfterFieldCompleteHook( result, error, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { const error = toGraphQLError( rawError, @@ -1905,7 +2069,18 @@ function invokeAfterFieldCompleteHook( "Unexpected error in afterFieldComplete hook", ); exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + resolveInfo.path, + "Unexpected error in afterFieldComplete hook", + ); + exeContext.errors.push(error); } + + return result; }, ); } @@ -1921,8 +2096,17 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { context: exeContext.contextValue, operation: exeContext.operation, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { + const error = toGraphQLError( + rawError, + undefined, + "Unexpected error in beforeOperationExecute hook", + ); + throw error; + } + + if (result instanceof Error) { const error = toGraphQLError( rawError, undefined, @@ -1930,6 +2114,8 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { ); exeContext.errors.push(error); } + + return result; }, ); } @@ -1949,7 +2135,7 @@ function invokeBeforeSubscriptionEventEmitHook( operation: exeContext.operation, eventPayload, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { const error = toGraphQLError( rawError, @@ -1957,7 +2143,18 @@ function invokeBeforeSubscriptionEventEmitHook( "Unexpected error in beforeSubscriptionEventEmit hook", ); exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + undefined, + "Unexpected error in beforeSubscriptionEventEmit hook", + ); + exeContext.errors.push(error); } + + return result; }, ); } @@ -1992,7 +2189,7 @@ function invokeAfterBuildResponseHook( function executeSafe( execute: () => T | Promise, - onComplete: (result: T | undefined, error: unknown) => void, + onComplete: (result: T | undefined, error: unknown) => T | Promise, ): T | Promise { let error: unknown; let result: T | Promise | undefined; @@ -2000,24 +2197,18 @@ function executeSafe( result = execute(); } catch (e) { error = e; - } finally { - if (!isPromise(result)) { - onComplete(result, error); - } } if (!isPromise(result)) { - return result as T; + return onComplete(result, error); } return result .then((hookResult) => { - onComplete(hookResult, error); - return hookResult; + return onComplete(hookResult, error); }) .catch((e) => { - onComplete(undefined, e); - return undefined; + return onComplete(undefined, e); }) as Promise; } diff --git a/packages/supermassive/src/hooks/types.ts b/packages/supermassive/src/hooks/types.ts index b41165c23..18be641f2 100644 --- a/packages/supermassive/src/hooks/types.ts +++ b/packages/supermassive/src/hooks/types.ts @@ -1,4 +1,4 @@ -import type { OperationDefinitionNode } from "graphql"; +import type { GraphQLError, OperationDefinitionNode } from "graphql"; import type { ResolveInfo, TotalExecutionResult } from "../types"; interface BaseExecuteHookArgs { @@ -21,6 +21,12 @@ export interface AfterFieldResolveHookArgs error?: unknown; } +export interface AfterFieldSubscribeHookArgs + extends PostExecuteFieldHookArgs { + result?: unknown; + error?: unknown; +} + export interface AfterFieldCompleteHookArgs extends PostExecuteFieldHookArgs { result?: unknown; @@ -48,7 +54,18 @@ export interface BeforeFieldResolveHook< > { (args: BaseExecuteFieldHookArgs): | Promise - | BeforeHookContext; + | BeforeHookContext + | GraphQLError; +} + +export interface BeforeFieldSubscribe< + ResolveContext = unknown, + BeforeHookContext = unknown, +> { + (args: BaseExecuteFieldHookArgs): + | Promise + | BeforeHookContext + | GraphQLError; } export interface AfterFieldResolveHook< @@ -56,16 +73,28 @@ export interface AfterFieldResolveHook< BeforeHookContext = unknown, AfterHookContext = BeforeHookContext, > { - ( - args: AfterFieldResolveHookArgs, - ): AfterHookContext; + (args: AfterFieldResolveHookArgs): + | AfterHookContext + | GraphQLError; +} + +export interface AfterFieldSubscribe< + ResolveContext = unknown, + BeforeHookContext = unknown, + AfterHookContext = BeforeHookContext, +> { + (args: AfterFieldSubscribeHookArgs): + | AfterHookContext + | GraphQLError; } export interface AfterFieldCompleteHook< ResolveContext = unknown, AfterHookContext = unknown, > { - (args: AfterFieldCompleteHookArgs): void; + ( + args: AfterFieldCompleteHookArgs, + ): void | GraphQLError; } export interface AfterBuildResponseHook { @@ -73,13 +102,17 @@ export interface AfterBuildResponseHook { } export interface BeforeOperationExecuteHook { - (args: BaseExecuteOperationHookArgs): void | Promise; + (args: BaseExecuteOperationHookArgs): + | void + | Promise + | GraphQLError; } export interface BeforeSubscriptionEventEmitHook { - ( - args: BeforeSubscriptionEventEmitHookArgs, - ): void | Promise; + (args: BeforeSubscriptionEventEmitHookArgs): + | void + | Promise + | GraphQLError; } export interface ExecutionHooks< @@ -93,11 +126,20 @@ export interface ExecutionHooks< ResolveContext, BeforeHookContext >; + beforeFieldSubscribe?: BeforeFieldSubscribe< + ResolveContext, + BeforeHookContext + >; afterFieldResolve?: AfterFieldResolveHook< ResolveContext, BeforeHookContext, AfterHookContext >; + afterFieldSubscribe?: AfterFieldSubscribe< + ResolveContext, + BeforeHookContext, + AfterHookContext + >; afterFieldComplete?: AfterFieldCompleteHook; afterBuildResponse?: AfterBuildResponseHook; } From 92579fb9b4bc8eddc04d0234be83105897f515ab Mon Sep 17 00:00:00 2001 From: Graphitation Service Account Date: Sun, 15 Dec 2024 19:04:32 +0000 Subject: [PATCH 04/21] applying package updates --- ...-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json | 7 ------- packages/supermassive/package.json | 2 +- packages/webpack-loader/package.json | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json diff --git a/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json b/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json deleted file mode 100644 index 5d757058d..000000000 --- a/change/@graphitation-supermassive-35e31e43-c61d-4e52-a933-e51e4a1aad72.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Supermassive hook error handling modified", - "packageName": "@graphitation/supermassive", - "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "none" -} diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index 933ad4716..cb8539ea6 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.8.0-alpha.0", + "version": "3.8.0-alpha.1", "main": "./src/index.ts", "repository": { "type": "git", diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json index adde05a68..6f60254c6 100644 --- a/packages/webpack-loader/package.json +++ b/packages/webpack-loader/package.json @@ -17,7 +17,7 @@ }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", - "@graphitation/supermassive": "^3.7.2" + "@graphitation/supermassive": "^3.8.0-alpha.1" }, "dependencies": { "@graphql-tools/optimize": "^1.1.1", From 451d6a80b3805d2ff6d6f4d7016462eeeb665a03 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:56:34 +0100 Subject: [PATCH 05/21] Supermassive bump (#493) Bump --- ...-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json | 7 +++++++ packages/supermassive/src/executeWithoutSchema.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json diff --git a/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json b/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json new file mode 100644 index 000000000..648327426 --- /dev/null +++ b/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix", + "packageName": "@graphitation/supermassive", + "email": "77059398+vejrj@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index 46f20a920..ed7e69b6f 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -1713,6 +1713,7 @@ function ensureValidRuntimeType( fieldGroup, ); } + if (typeof runtimeTypeName !== "string") { throw locatedError( `Abstract type "${returnTypeName}" must resolve to an Object type at runtime for field "${info.returnTypeName}.${info.fieldName}" with ` + From e7f15fff0165e6410f2ca69089051a7e33b5119b Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:51:26 +0100 Subject: [PATCH 06/21] bump --- ...-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json | 7 ------- packages/supermassive/package.json | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json diff --git a/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json b/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json deleted file mode 100644 index 648327426..000000000 --- a/change/@graphitation-supermassive-5e0c6d7c-15e2-4b35-86cd-c3e579dd2188.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "fix", - "packageName": "@graphitation/supermassive", - "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "none" -} diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index cb8539ea6..1d648468f 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.8.0-alpha.1", + "version": "3.8.0-alpha.2", "main": "./src/index.ts", "repository": { "type": "git", From ab7bfb9542610cc2e6b2d2cf523817fdeac927a7 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:30:34 +0100 Subject: [PATCH 07/21] Subscribe hooks fixed (#494) * subscribe hooks fixed --- ...-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json | 7 + .../supermassive/src/__tests__/hooks.test.ts | 122 +++++++++++++++--- .../supermassive/src/executeWithoutSchema.ts | 97 ++++++-------- packages/supermassive/src/hooks/types.ts | 5 +- 4 files changed, 150 insertions(+), 81 deletions(-) create mode 100644 change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json diff --git a/change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json b/change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json new file mode 100644 index 000000000..a564057e6 --- /dev/null +++ b/change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "subscribe hooks fixed", + "packageName": "@graphitation/supermassive", + "email": "77059398+vejrj@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/packages/supermassive/src/__tests__/hooks.test.ts b/packages/supermassive/src/__tests__/hooks.test.ts index 77471e53b..2c18f90b6 100644 --- a/packages/supermassive/src/__tests__/hooks.test.ts +++ b/packages/supermassive/src/__tests__/hooks.test.ts @@ -1093,7 +1093,7 @@ describe.each([ }); }); - describe("error in subscription is thrown", () => { + describe("error in beforeSubscriptionEventEmit", () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -1168,14 +1168,14 @@ describe.each([ ); }); - describe("Error in subscription during creating event stream", () => { + describe("afterFieldSubscribe and beforeFieldSubscribe hook errors during creating event stream. It should throw if an error is thrown or returned", () => { beforeEach(() => { jest.clearAllMocks(); }); const testCases: Array = [ { - name: "beforeFieldSubscribe (Error is returned)", + name: "afterFieldSubscribe (Error is returned)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1186,15 +1186,15 @@ describe.each([ limit: 1, }, hooks: { - beforeFieldSubscribe: jest.fn().mockImplementation(() => { + afterFieldSubscribe: jest.fn().mockImplementation(() => { return new Error("Hook error"); }), }, expectedErrorMessage: - "Unexpected error in beforeFieldSubscribe hook: Hook error", + "Unexpected error in afterFieldSubscribe hook: Hook error", }, { - name: "afterFieldSubscribe (Error is returned)", + name: "afterFieldSubscribe (Error is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1206,14 +1206,14 @@ describe.each([ }, hooks: { afterFieldSubscribe: jest.fn().mockImplementation(() => { - return new Error("Hook error"); + throw new Error("Hook error"); }), }, expectedErrorMessage: "Unexpected error in afterFieldSubscribe hook: Hook error", }, { - name: "beforeFieldSubscribe (Error is thrown)", + name: "afterFieldSubscribe (string is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1224,15 +1224,15 @@ describe.each([ limit: 1, }, hooks: { - beforeFieldSubscribe: jest.fn().mockImplementation(() => { - throw new Error("Hook error"); + afterFieldSubscribe: jest.fn().mockImplementation(() => { + throw "Hook error"; }), }, expectedErrorMessage: - "Unexpected error in beforeFieldSubscribe hook: Hook error", + 'Unexpected error in afterFieldSubscribe hook: "Hook error"', }, { - name: "beforeFieldSubscribe (string is thrown)", + name: "beforeFieldSubscribe (Error is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1244,14 +1244,14 @@ describe.each([ }, hooks: { beforeFieldSubscribe: jest.fn().mockImplementation(() => { - throw "Hook error"; + throw new Error("Hook error"); }), }, expectedErrorMessage: - 'Unexpected error in beforeFieldSubscribe hook: "Hook error"', + "Unexpected error in beforeFieldSubscribe hook: Hook error", }, { - name: "afterFieldSubscribe (Error is thrown)", + name: "beforeFieldSubscribe (Error is returned)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1262,15 +1262,15 @@ describe.each([ limit: 1, }, hooks: { - afterFieldSubscribe: jest.fn().mockImplementation(() => { - throw new Error("Hook error"); + beforeFieldSubscribe: jest.fn().mockImplementation(() => { + return new Error("Hook error"); }), }, expectedErrorMessage: - "Unexpected error in afterFieldSubscribe hook: Hook error", + "Unexpected error in beforeFieldSubscribe hook: Hook error", }, { - name: "afterFieldSubscribe (string is thrown)", + name: "beforeFieldSubscribe (string is thrown)", document: `subscription EmitPersons($limit: Int!) { emitPersons(limit: $limit) { @@ -1281,12 +1281,12 @@ describe.each([ limit: 1, }, hooks: { - afterFieldSubscribe: jest.fn().mockImplementation(() => { + beforeFieldSubscribe: jest.fn().mockImplementation(() => { throw "Hook error"; }), }, expectedErrorMessage: - 'Unexpected error in afterFieldSubscribe hook: "Hook error"', + 'Unexpected error in beforeFieldSubscribe hook: "Hook error"', }, ]; @@ -1527,6 +1527,70 @@ describe.each([ ); }); + describe("Error in afterBuildResponse", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("afterBuildResponse (Error is thrown)", async () => { + expect.assertions(5); + + const response = await drainExecution( + await execute( + parse(`{ + film(id: 1) { + title + } + }`), + resolvers as UserResolvers, + { + afterBuildResponse: jest.fn().mockImplementation(() => { + throw new Error("Hook error"); + }), + }, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + expect(result.data).toBeUndefined(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe( + "Unexpected error in afterBuildResponse hook: Hook error", + ); + }); + + test("afterBuildResponse (Error is returned)", async () => { + expect.assertions(5); + + const response = await drainExecution( + await execute( + parse(`{ + film(id: 1) { + title + } + }`), + resolvers as UserResolvers, + { + afterBuildResponse: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), + }, + ), + ); + const result = Array.isArray(response) ? response[0] : response; + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + expect(result.data).toBeDefined(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe( + "Unexpected error in afterBuildResponse hook: Hook error", + ); + }); + }); + describe("Error thrown in the BEFORE OPERATION hook breaks execution", () => { beforeEach(() => { jest.clearAllMocks(); @@ -1608,6 +1672,22 @@ describe.each([ expectedErrorMessage: "Unexpected error in beforeFieldResolve hook: Hook error", }, + { + name: "beforeOperationExecute (Error is thrown)", + document: ` + { + film(id: 1) { + title + } + }`, + hooks: { + beforeOperationExecute: jest.fn().mockImplementation(() => { + return new Error("Hook error"); + }), + }, + expectedErrorMessage: + "Unexpected error in beforeOperationExecute hook: Hook error", + }, { name: "afterFieldResolve (Error is thrown)", document: ` diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index ed7e69b6f..4309285c8 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -401,10 +401,16 @@ function buildResponse( }; } else { if (hooks?.afterBuildResponse) { - invokeAfterBuildResponseHook(exeContext, initialResult); + const hookResult = invokeAfterBuildResponseHook( + exeContext, + initialResult, + ); if (exeContext.errors.length > (initialResult.errors?.length ?? 0)) { initialResult.errors = exeContext.errors; } + if (hookResult instanceof GraphQLError) { + return { errors: initialResult.errors }; + } } return initialResult; } @@ -693,6 +699,12 @@ function executeSubscriptionImpl( // Implements the "ResolveFieldEventStream" algorithm from GraphQL specification. // It differs from "ResolveFieldValue" due to providing a different `resolveFn`. + let result: unknown; + + if (!isDefaultResolverUsed && hooks?.beforeFieldSubscribe) { + hookContext = invokeBeforeFieldSubscribeHook(info, exeContext); + } + // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. const args = getArgumentValues(exeContext, fieldDef, fieldGroup[0]); @@ -702,27 +714,16 @@ function executeSubscriptionImpl( // used to represent an authenticated user, or request-specific caches. const contextValue = exeContext.contextValue; - if (!isDefaultResolverUsed && hooks?.beforeFieldSubscribe) { - hookContext = invokeBeforeFieldSubscribeHook(info, exeContext); - } - - let result: unknown; - if (hookContext) { if (isPromise(hookContext)) { result = hookContext.then((context) => { hookContext = context; - if (hookContext instanceof GraphQLError) { - return null; - } - return resolveFn(rootValue, args, contextValue, info); }); - } else if (hookContext instanceof GraphQLError) { - result = null; } } + // Call the `subscribe()` resolver or the default resolver to produce an // AsyncIterable yielding raw payloads. if (result === undefined) { @@ -738,40 +739,20 @@ function executeSubscriptionImpl( resolved, error, ); - - if (hookContext instanceof GraphQLError) { - throw hookContext; - } } }; if (isPromise(result)) { - return result.then(assertEventStream).then( - (resolved) => { - if (resolved instanceof GraphQLError) { - throw resolved; - } - - if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { - hookContext = invokeAfterFieldSubscribeHook( - info, - exeContext, - hookContext, - resolved, - ); - - if (hookContext instanceof GraphQLError) { - throw hookContext; - } - } - return resolved; - }, - (error) => { + return result + .then(assertEventStream, (error) => { afterFieldSubscribeHandle(undefined, error); - throw locatedError(error, fieldGroup, pathToArray(path)); - }, - ); + }) + .then((resolved) => { + afterFieldSubscribeHandle(resolved); + + return resolved; + }); } const stream = assertEventStream(result); @@ -779,7 +760,7 @@ function executeSubscriptionImpl( return stream; } catch (error) { if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { - hookContext = invokeAfterFieldSubscribeHook( + invokeAfterFieldSubscribeHook( info, exeContext, hookContext, @@ -788,10 +769,6 @@ function executeSubscriptionImpl( ); } - if (hookContext instanceof GraphQLError) { - throw hookContext; - } - throw locatedError(error, fieldGroup, pathToArray(path)); } } @@ -1091,15 +1068,11 @@ function resolveAndCompleteField( (rawError) => { const error = locatedError(rawError, fieldGroup, pathToArray(path)); - const hookResult = handleAfterFieldHooks( + handleAfterFieldHooks( invokeAfterFieldCompleteHook, !!hooks?.afterFieldComplete, )(undefined, error); - if (hookResult === null) { - return null; - } - handleFieldError( rawError, exeContext, @@ -1896,7 +1869,7 @@ function invokeBeforeFieldSubscribeHook( ); exeContext.errors.push(error); - return error; + throw error; } else if (result instanceof Error) { const error = toGraphQLError( result, @@ -1905,7 +1878,7 @@ function invokeBeforeFieldSubscribeHook( ); exeContext.errors.push(error); - return error; + throw error; } return result; @@ -2025,7 +1998,7 @@ function invokeAfterFieldSubscribeHook( ); exeContext.errors.push(error); - return error; + throw error; } else if (result instanceof Error) { const error = toGraphQLError( result, @@ -2034,7 +2007,7 @@ function invokeAfterFieldSubscribeHook( ); exeContext.errors.push(error); - return error; + throw error; } return result; @@ -2109,7 +2082,7 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { if (result instanceof Error) { const error = toGraphQLError( - rawError, + result, undefined, "Unexpected error in beforeOperationExecute hook", ); @@ -2175,7 +2148,7 @@ function invokeAfterBuildResponseHook( operation: exeContext.operation, result, }), - (_, rawError) => { + (result, rawError) => { if (rawError) { const error = toGraphQLError( rawError, @@ -2183,6 +2156,16 @@ function invokeAfterBuildResponseHook( "Unexpected error in afterBuildResponse hook", ); exeContext.errors.push(error); + + return error; + } else if (result instanceof Error) { + const error = toGraphQLError( + result, + undefined, + "Unexpected error in afterBuildResponse hook", + ); + + exeContext.errors.push(error); } }, ); diff --git a/packages/supermassive/src/hooks/types.ts b/packages/supermassive/src/hooks/types.ts index 18be641f2..213606852 100644 --- a/packages/supermassive/src/hooks/types.ts +++ b/packages/supermassive/src/hooks/types.ts @@ -64,8 +64,7 @@ export interface BeforeFieldSubscribe< > { (args: BaseExecuteFieldHookArgs): | Promise - | BeforeHookContext - | GraphQLError; + | BeforeHookContext; } export interface AfterFieldResolveHook< @@ -98,7 +97,7 @@ export interface AfterFieldCompleteHook< } export interface AfterBuildResponseHook { - (args: AfterBuildResponseHookArgs): void; + (args: AfterBuildResponseHookArgs): void | GraphQLError; } export interface BeforeOperationExecuteHook { From 9213929112b0b1b0b5fb19216e6811dc5f8c663b Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:39:16 +0100 Subject: [PATCH 08/21] fix --- packages/supermassive/src/executeWithoutSchema.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index 4309285c8..75c206662 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -1112,17 +1112,13 @@ function resolveAndCompleteField( ); } if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { - const invokeAfterFieldCompleteHookResult = invokeAfterFieldCompleteHook( + invokeAfterFieldCompleteHook( info, exeContext, hookContext, undefined, error, ); - - if (invokeAfterFieldCompleteHookResult instanceof GraphQLError) { - return null; - } } handleFieldError( From 5e40b942627b53590a09dc17459bd90d6bb61099 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:02:03 +0100 Subject: [PATCH 09/21] Revert "adding realese config" This reverts commit 75c33b3aa2dac2e6d295da8c554bde13de904112. --- .azure-devops/graphitation-release.yml | 1 - package.json | 2 +- packages/supermassive/package.json | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.azure-devops/graphitation-release.yml b/.azure-devops/graphitation-release.yml index c54cd8f8d..e5ff2a284 100644 --- a/.azure-devops/graphitation-release.yml +++ b/.azure-devops/graphitation-release.yml @@ -2,7 +2,6 @@ pr: none trigger: - main - alloy/relay-apollo-duct-tape - - jvejr/supermassive-hooks-error-handling-alpha variables: - group: InfoSec-SecurityResults diff --git a/package.json b/package.json index 57645c2b6..2f9ee7aa1 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "beachball": "beachball -b origin/jvejr/supermassive-hooks-error-handling-alpha", "change": "yarn beachball change", "checkchange": "yarn beachball check", - "release": "yarn beachball publish -t alpha", + "release": "yarn beachball publish -t latest", "postinstall": "patch-package" }, "devDependencies": { diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index 1d648468f..936979128 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.8.0-alpha.2", + "version": "3.7.2", "main": "./src/index.ts", "repository": { "type": "git", From 62311899d1addbb4545e71a706fdb917544f9357 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:02:29 +0100 Subject: [PATCH 10/21] config revert --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f9ee7aa1..6d0c7990b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint": "lage lint --continue", "lage": "lage", "ci": "yarn lage build types test lint && yarn checkchange", - "beachball": "beachball -b origin/jvejr/supermassive-hooks-error-handling-alpha", + "beachball": "beachball -b origin/main", "change": "yarn beachball change", "checkchange": "yarn beachball check", "release": "yarn beachball publish -t latest", From df2600f58b412f00d632ae3b032825e54277b399 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:05:59 +0100 Subject: [PATCH 11/21] Change files --- ...supermassive-d7732868-005b-48dd-b592-356b261cde40.json} | 6 +++--- ...ebpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) rename change/{@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json => @graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json} (52%) create mode 100644 change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json diff --git a/change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json b/change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json similarity index 52% rename from change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json rename to change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json index a564057e6..b07b4f683 100644 --- a/change/@graphitation-supermassive-b4309e82-a399-4bdb-86a0-43dde45b1ac6.json +++ b/change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json @@ -1,7 +1,7 @@ { - "type": "prerelease", - "comment": "subscribe hooks fixed", + "type": "minor", + "comment": "Hooks error handling", "packageName": "@graphitation/supermassive", "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "none" + "dependentChangeType": "patch" } diff --git a/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json b/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json new file mode 100644 index 000000000..7c0f4a393 --- /dev/null +++ b/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Hooks error handling", + "packageName": "@graphitation/webpack-loader", + "email": "77059398+vejrj@users.noreply.github.com", + "dependentChangeType": "patch" +} From b090fc33538767ab451323fb4168b47473dc4687 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:28:21 +0100 Subject: [PATCH 12/21] fix --- ...ebpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json | 7 ------- packages/webpack-loader/package.json | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json diff --git a/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json b/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json deleted file mode 100644 index 7c0f4a393..000000000 --- a/change/@graphitation-webpack-loader-ea4bf97a-9ed2-4c13-a314-9c991e3a5326.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "patch", - "comment": "Hooks error handling", - "packageName": "@graphitation/webpack-loader", - "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "patch" -} diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json index 6f60254c6..adde05a68 100644 --- a/packages/webpack-loader/package.json +++ b/packages/webpack-loader/package.json @@ -17,7 +17,7 @@ }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", - "@graphitation/supermassive": "^3.8.0-alpha.1" + "@graphitation/supermassive": "^3.7.2" }, "dependencies": { "@graphql-tools/optimize": "^1.1.1", From dfc43677de6910841ab2f1af3143959578c94a68 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:19:24 +0100 Subject: [PATCH 13/21] types fixed before operation execution reverted --- .../supermassive/src/__tests__/hooks.test.ts | 20 ++++++++++++----- .../supermassive/src/executeWithoutSchema.ts | 15 +++++++++++-- packages/supermassive/src/hooks/types.ts | 22 +++++++++++-------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/packages/supermassive/src/__tests__/hooks.test.ts b/packages/supermassive/src/__tests__/hooks.test.ts index 2c18f90b6..f7178bc5e 100644 --- a/packages/supermassive/src/__tests__/hooks.test.ts +++ b/packages/supermassive/src/__tests__/hooks.test.ts @@ -1634,18 +1634,26 @@ describe.each([ it.each(testCases)( "$name", async ({ document, hooks, expectedErrorMessage, variables }) => { - expect.assertions(1); + expect.assertions(5); const parsedDocument = parse(document); - await expect(async function () { - const result = await execute( + const response = await drainExecution( + await execute( parsedDocument, resolvers as UserResolvers, hooks, variables, - ); - return drainExecution(result); - }).rejects.toThrow(expectedErrorMessage); + ), + ); + + const result = Array.isArray(response) ? response[0] : response; + expect(isTotalExecutionResult(result)).toBe(true); + const errors = result.errors; + + expect(result.data).toBeNull(); + expect(errors).toBeDefined(); + expect(errors).toHaveLength(1); + expect(errors?.[0].message).toBe(expectedErrorMessage); }, ); }); diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index 75c206662..a4a0794be 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -286,8 +286,17 @@ function executeOperationWithBeforeHook( hook = invokeBeforeOperationExecuteHook(exeContext); } + if (hook instanceof GraphQLError) { + return buildResponse(exeContext, null); + } + if (isPromise(hook)) { - return hook.then(() => executeOperation(exeContext)); + return hook.then((hookResult) => { + if (hookResult instanceof GraphQLError) { + return buildResponse(exeContext, null); + } + return executeOperation(exeContext); + }); } return executeOperation(exeContext); @@ -2073,7 +2082,9 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { undefined, "Unexpected error in beforeOperationExecute hook", ); - throw error; + exeContext.errors.push(error); + + return error; } if (result instanceof Error) { diff --git a/packages/supermassive/src/hooks/types.ts b/packages/supermassive/src/hooks/types.ts index 213606852..027805c4b 100644 --- a/packages/supermassive/src/hooks/types.ts +++ b/packages/supermassive/src/hooks/types.ts @@ -1,4 +1,4 @@ -import type { GraphQLError, OperationDefinitionNode } from "graphql"; +import type { OperationDefinitionNode } from "graphql"; import type { ResolveInfo, TotalExecutionResult } from "../types"; interface BaseExecuteHookArgs { @@ -55,7 +55,7 @@ export interface BeforeFieldResolveHook< (args: BaseExecuteFieldHookArgs): | Promise | BeforeHookContext - | GraphQLError; + | Error; } export interface BeforeFieldSubscribe< @@ -64,7 +64,9 @@ export interface BeforeFieldSubscribe< > { (args: BaseExecuteFieldHookArgs): | Promise - | BeforeHookContext; + | Promise + | BeforeHookContext + | Error; } export interface AfterFieldResolveHook< @@ -74,7 +76,7 @@ export interface AfterFieldResolveHook< > { (args: AfterFieldResolveHookArgs): | AfterHookContext - | GraphQLError; + | Error; } export interface AfterFieldSubscribe< @@ -84,7 +86,7 @@ export interface AfterFieldSubscribe< > { (args: AfterFieldSubscribeHookArgs): | AfterHookContext - | GraphQLError; + | Error; } export interface AfterFieldCompleteHook< @@ -93,25 +95,27 @@ export interface AfterFieldCompleteHook< > { ( args: AfterFieldCompleteHookArgs, - ): void | GraphQLError; + ): void | Error; } export interface AfterBuildResponseHook { - (args: AfterBuildResponseHookArgs): void | GraphQLError; + (args: AfterBuildResponseHookArgs): void | Error; } export interface BeforeOperationExecuteHook { (args: BaseExecuteOperationHookArgs): | void | Promise - | GraphQLError; + | Error + | Promise; } export interface BeforeSubscriptionEventEmitHook { (args: BeforeSubscriptionEventEmitHookArgs): | void | Promise - | GraphQLError; + | Error + | Promise; } export interface ExecutionHooks< From 4abcb9f2cfe455ab4b317185edf2c646c61fe554 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:47:48 +0100 Subject: [PATCH 14/21] hooks doc added --- packages/supermassive/hooks.md | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/supermassive/hooks.md diff --git a/packages/supermassive/hooks.md b/packages/supermassive/hooks.md new file mode 100644 index 000000000..1fad070df --- /dev/null +++ b/packages/supermassive/hooks.md @@ -0,0 +1,52 @@ +# Hooks Documentation + +## Overview + +This document describes the behaviour of hooks when they encounter errors. + +## General Rule + +- **Returned Errors**: The error is registered and execution continues. +- **Thrown Errors**: Specific behaviour is applied based on the hook. + +### Hooks and Their Behaviours + +#### `beforeOperationExecute` + +- **Thrown Error**: Stops execution and sets `data` to `null` and registers the error. +- **Returned Errors**: Registers the error and moves one. + +#### `beforeSubscriptionEventEmit` + +- **Thrown Error**: Sets `data` to `null` and registers the error. +- **Returned Errors**: Registers the error and moves one. + +#### `beforeFieldResolve` + +- **Thrown Error**: The field is not executed and is handled as if it has returned `null`. +- **Returned Errors**: Registers the error and moves one. + +#### `afterFieldResolve` + +- **Thrown Error**: The field is set to `null` and the error is registered. +- **Returned Errors**: Registers the error and moves one. + +#### `afterFieldComplete` + +- **Thrown Error**: The field is set to `null` and the error is registered. +- **Returned Errors**: Registers the error and moves one. + +#### `afterBuildResponse` + +- **Thrown Error**: Returns no data property, only errors. +- **Returned Errors**: Registers the error and moves one. + +## Additional Hooks (Update 9.12) + +### `beforeFieldSubscribe` + +- **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. + +### `afterFieldSubscribe` + +- **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. From 4b93dd80e985dc193df6f89005d5ec17ece26ab6 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 18 Dec 2024 20:26:01 +0100 Subject: [PATCH 15/21] fix --- packages/supermassive/hooks.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/supermassive/hooks.md b/packages/supermassive/hooks.md index 1fad070df..c6f867c0a 100644 --- a/packages/supermassive/hooks.md +++ b/packages/supermassive/hooks.md @@ -13,6 +13,8 @@ This document describes the behaviour of hooks when they encounter errors. #### `beforeOperationExecute` +Called before every operation + - **Thrown Error**: Stops execution and sets `data` to `null` and registers the error. - **Returned Errors**: Registers the error and moves one. @@ -23,16 +25,22 @@ This document describes the behaviour of hooks when they encounter errors. #### `beforeFieldResolve` +Called before every field resolution + - **Thrown Error**: The field is not executed and is handled as if it has returned `null`. - **Returned Errors**: Registers the error and moves one. #### `afterFieldResolve` +Called after every field resolution. + - **Thrown Error**: The field is set to `null` and the error is registered. - **Returned Errors**: Registers the error and moves one. #### `afterFieldComplete` +Called when field value is completed + - **Thrown Error**: The field is set to `null` and the error is registered. - **Returned Errors**: Registers the error and moves one. @@ -41,12 +49,16 @@ This document describes the behaviour of hooks when they encounter errors. - **Thrown Error**: Returns no data property, only errors. - **Returned Errors**: Registers the error and moves one. -## Additional Hooks (Update 9.12) +## Additional Hooks ### `beforeFieldSubscribe` +Called before subscription event stream creation + - **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. ### `afterFieldSubscribe` +Called after subscription event stream creation + - **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. From f169dbe58dd2903ee5be86ad3472350750dabac0 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:38:16 +0100 Subject: [PATCH 16/21] CR fix --- packages/supermassive/hooks.md | 22 ++--- .../supermassive/src/executeWithoutSchema.ts | 10 +-- packages/supermassive/src/hooks/types.ts | 84 +++++++++++++++---- 3 files changed, 85 insertions(+), 31 deletions(-) diff --git a/packages/supermassive/hooks.md b/packages/supermassive/hooks.md index c6f867c0a..0298fa034 100644 --- a/packages/supermassive/hooks.md +++ b/packages/supermassive/hooks.md @@ -6,8 +6,8 @@ This document describes the behaviour of hooks when they encounter errors. ## General Rule -- **Returned Errors**: The error is registered and execution continues. -- **Thrown Errors**: Specific behaviour is applied based on the hook. +- **Thrown Error**: Specific behaviour is applied based on the hook. +- **Returned Error**: The error is registered and execution continues. ### Hooks and Their Behaviours @@ -16,38 +16,38 @@ This document describes the behaviour of hooks when they encounter errors. Called before every operation - **Thrown Error**: Stops execution and sets `data` to `null` and registers the error. -- **Returned Errors**: Registers the error and moves one. +- **Returned Error**: The error is registered and execution continues. #### `beforeSubscriptionEventEmit` -- **Thrown Error**: Sets `data` to `null` and registers the error. -- **Returned Errors**: Registers the error and moves one. +- **Thrown ErErrorror**: Sets `data` to `null` and registers the error. +- **Returned Error**: The error is registered and execution continues. #### `beforeFieldResolve` Called before every field resolution - **Thrown Error**: The field is not executed and is handled as if it has returned `null`. -- **Returned Errors**: Registers the error and moves one. +- **Returned Error**: The error is registered and execution continues. #### `afterFieldResolve` Called after every field resolution. - **Thrown Error**: The field is set to `null` and the error is registered. -- **Returned Errors**: Registers the error and moves one. +- **Returned Error**: The error is registered and execution continues. #### `afterFieldComplete` Called when field value is completed - **Thrown Error**: The field is set to `null` and the error is registered. -- **Returned Errors**: Registers the error and moves one. +- **Returned Error**: The error is registered and execution continues. #### `afterBuildResponse` - **Thrown Error**: Returns no data property, only errors. -- **Returned Errors**: Registers the error and moves one. +- **Returned Error**: The error is registered and execution continues. ## Additional Hooks @@ -55,10 +55,10 @@ Called when field value is completed Called before subscription event stream creation -- **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. +- **Thrown or Returned Error**: Stops execution and sets `data` is `undefined` and error is returned in `errors` field. ### `afterFieldSubscribe` Called after subscription event stream creation -- **Thrown or Returned Error**: Throws the error regardless of whether it is returned or thrown. +- **Thrown or Returned Error**: Stops execution and sets `data` is `undefined` and error is returned in `errors` field. diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index a4a0794be..76249f476 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -281,17 +281,17 @@ function executeOperationWithBeforeHook( exeContext: ExecutionContext, ): PromiseOrValue { const hooks = exeContext.fieldExecutionHooks; - let hook; + let hookResultPromise; if (hooks?.beforeOperationExecute) { - hook = invokeBeforeOperationExecuteHook(exeContext); + hookResultPromise = invokeBeforeOperationExecuteHook(exeContext); } - if (hook instanceof GraphQLError) { + if (hookResultPromise instanceof GraphQLError) { return buildResponse(exeContext, null); } - if (isPromise(hook)) { - return hook.then((hookResult) => { + if (isPromise(hookResultPromise)) { + return hookResultPromise.then((hookResult) => { if (hookResult instanceof GraphQLError) { return buildResponse(exeContext, null); } diff --git a/packages/supermassive/src/hooks/types.ts b/packages/supermassive/src/hooks/types.ts index 027805c4b..aafdd7071 100644 --- a/packages/supermassive/src/hooks/types.ts +++ b/packages/supermassive/src/hooks/types.ts @@ -1,5 +1,6 @@ import type { OperationDefinitionNode } from "graphql"; import type { ResolveInfo, TotalExecutionResult } from "../types"; +import { PromiseOrValue } from "../jsutils/PromiseOrValue"; interface BaseExecuteHookArgs { context: ResolveContext; @@ -53,20 +54,20 @@ export interface BeforeFieldResolveHook< BeforeHookContext = unknown, > { (args: BaseExecuteFieldHookArgs): - | Promise - | BeforeHookContext - | Error; + | PromiseOrValue + | PromiseOrValue; } +/** + * Represents a user in the system. + */ export interface BeforeFieldSubscribe< ResolveContext = unknown, BeforeHookContext = unknown, > { (args: BaseExecuteFieldHookArgs): - | Promise - | Promise - | BeforeHookContext - | Error; + | PromiseOrValue + | PromiseOrValue; } export interface AfterFieldResolveHook< @@ -104,18 +105,14 @@ export interface AfterBuildResponseHook { export interface BeforeOperationExecuteHook { (args: BaseExecuteOperationHookArgs): - | void - | Promise - | Error - | Promise; + | PromiseOrValue + | PromiseOrValue; } export interface BeforeSubscriptionEventEmitHook { (args: BeforeSubscriptionEventEmitHookArgs): - | void - | Promise - | Error - | Promise; + | PromiseOrValue + | PromiseOrValue; } export interface ExecutionHooks< @@ -123,26 +120,83 @@ export interface ExecutionHooks< BeforeHookContext = unknown, AfterHookContext = BeforeHookContext, > { + /** + * Called before every operation. + * + * @hook + * @throws {Error} Stops execution and sets `data` to `null` and registers the error. + * @returns {Error} The error is registered and execution continues. + */ beforeOperationExecute?: BeforeOperationExecuteHook; + /** + * Called before every subscription event emit. + * + * @hook + * @throws {Error} Sets `data` to `null` and registers the error. + * @returns {Error} The error is registered and execution continues. + */ beforeSubscriptionEventEmit?: BeforeSubscriptionEventEmitHook; + /** + * Called before every field resolution. + * + * @hook + * @throws {Error} The field is not executed and is handled as if it has returned `null`. + * @returns {Error} The error is registered and execution continues. + */ beforeFieldResolve?: BeforeFieldResolveHook< ResolveContext, BeforeHookContext >; + /** + * Called before subscription event stream creation. + * + * @hook + * @throws {Error} Stops execution and sets `data` to `undefined` and error is returned in `errors` field. + * @returns {Error} Stops execution and sets `data` to `undefined` and error is returned in `errors` field. + */ beforeFieldSubscribe?: BeforeFieldSubscribe< ResolveContext, BeforeHookContext >; + /** + * Called after every field resolution. + * + * @hook + * @throws {Error} The field is set to `null` and the error is registered. + * @returns {Error} The error is registered and execution continues. + */ afterFieldResolve?: AfterFieldResolveHook< ResolveContext, BeforeHookContext, AfterHookContext >; + + /** + * Called after subscription event stream creation. + * + * @hook + * @throws {Error} Stops execution and sets `data` to `undefined` and error is returned in `errors` field. + * @returns {Error} Stops execution and sets `data` to `undefined` and error is returned in `errors` field. + */ afterFieldSubscribe?: AfterFieldSubscribe< ResolveContext, BeforeHookContext, AfterHookContext >; + /** + * Called when field value is completed. + * + * @hook + * @throws {Error} The field is set to `null` and the error is registered. + * @returns {Error} The error is registered and execution continues. + */ afterFieldComplete?: AfterFieldCompleteHook; + /** + * Called after the response is built. + * + * @hook + * @throws {Error} Returns no data property, only errors. + * @returns {Error} The error is registered and execution continues. + */ afterBuildResponse?: AfterBuildResponseHook; } From bbb02c38848ad0f5a92c4e9fc69dc0b58575c320 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:25:51 +0100 Subject: [PATCH 17/21] CR fix --- .../supermassive/src/__tests__/hooks.test.ts | 56 +++++++++---------- .../supermassive/src/executeWithoutSchema.ts | 35 ++++++------ 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/supermassive/src/__tests__/hooks.test.ts b/packages/supermassive/src/__tests__/hooks.test.ts index f7178bc5e..7b40046cc 100644 --- a/packages/supermassive/src/__tests__/hooks.test.ts +++ b/packages/supermassive/src/__tests__/hooks.test.ts @@ -1116,7 +1116,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + "Unexpected error thrown by beforeSubscriptionEventEmit hook (operation: EmitPersons): Hook error", }, { name: "async beforeSubscriptionEventEmit (Error is thrown)", @@ -1137,7 +1137,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + "Unexpected error thrown by beforeSubscriptionEventEmit hook (operation: EmitPersons): Hook error", }, ]; @@ -1191,7 +1191,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldSubscribe hook: Hook error", + "Unexpected error returned from afterFieldSubscribe hook: Hook error", }, { name: "afterFieldSubscribe (Error is thrown)", @@ -1210,7 +1210,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldSubscribe hook: Hook error", + "Unexpected error thrown by afterFieldSubscribe hook: Hook error", }, { name: "afterFieldSubscribe (string is thrown)", @@ -1229,7 +1229,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in afterFieldSubscribe hook: "Hook error"', + 'Unexpected error thrown by afterFieldSubscribe hook: "Hook error"', }, { name: "beforeFieldSubscribe (Error is thrown)", @@ -1248,7 +1248,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeFieldSubscribe hook: Hook error", + "Unexpected error thrown by beforeFieldSubscribe hook: Hook error", }, { name: "beforeFieldSubscribe (Error is returned)", @@ -1267,7 +1267,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeFieldSubscribe hook: Hook error", + "Unexpected error returned from beforeFieldSubscribe hook: Hook error", }, { name: "beforeFieldSubscribe (string is thrown)", @@ -1286,7 +1286,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in beforeFieldSubscribe hook: "Hook error"', + 'Unexpected error thrown by beforeFieldSubscribe hook: "Hook error"', }, ]; @@ -1335,7 +1335,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeFieldResolve hook: Hook error", + "Unexpected error thrown by beforeFieldResolve hook: Hook error", }, { name: "beforeFieldResolve (string is thrown)", @@ -1351,7 +1351,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in beforeFieldResolve hook: "Hook error"', + 'Unexpected error thrown by beforeFieldResolve hook: "Hook error"', }, { name: "afterFieldResolve (Error is thrown)", @@ -1367,7 +1367,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldResolve hook: Hook error", + "Unexpected error thrown by afterFieldResolve hook: Hook error", }, { name: "afterFieldResolve (string is thrown)", @@ -1383,7 +1383,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in afterFieldResolve hook: "Hook error"', + 'Unexpected error thrown by afterFieldResolve hook: "Hook error"', }, { name: "afterFieldComplete (Error is thrown)", @@ -1399,7 +1399,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldComplete hook: Hook error", + "Unexpected error thrown by afterFieldComplete hook: Hook error", }, { name: "afterFieldComplete (string is thrown)", @@ -1415,7 +1415,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in afterFieldComplete hook: "Hook error"', + 'Unexpected error thrown by afterFieldComplete hook: "Hook error"', }, ]; @@ -1466,7 +1466,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeFieldResolve hook: Hook error", + "Unexpected error returned from beforeFieldResolve hook: Hook error", }, { name: "afterFieldResolve (Error is returned)", @@ -1482,7 +1482,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldResolve hook: Hook error", + "Unexpected error returned from afterFieldResolve hook: Hook error", }, { name: "afterFieldComplete (Error is returned)", @@ -1498,7 +1498,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldComplete hook: Hook error", + "Unexpected error returned from afterFieldComplete hook: Hook error", }, ]; @@ -1537,7 +1537,7 @@ describe.each([ const response = await drainExecution( await execute( - parse(`{ + parse(`query GetFilm{ film(id: 1) { title } @@ -1557,7 +1557,7 @@ describe.each([ expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors?.[0].message).toBe( - "Unexpected error in afterBuildResponse hook: Hook error", + "Unexpected error thrown by afterBuildResponse hook (operation: GetFilm): Hook error", ); }); @@ -1586,7 +1586,7 @@ describe.each([ expect(errors).toBeDefined(); expect(errors).toHaveLength(1); expect(errors?.[0].message).toBe( - "Unexpected error in afterBuildResponse hook: Hook error", + "Unexpected error returned from afterBuildResponse hook (operation: unknown): Hook error", ); }); }); @@ -1611,7 +1611,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeOperationExecute hook: Hook error", + "Unexpected error thrown by beforeOperationExecute hook (operation: unknown): Hook error", }, { name: "beforeOperationExecute (string is thrown)", @@ -1627,7 +1627,7 @@ describe.each([ }), }, expectedErrorMessage: - 'Unexpected error in beforeOperationExecute hook: "Hook error"', + 'Unexpected error thrown by beforeOperationExecute hook (operation: unknown): "Hook error"', }, ]; @@ -1678,7 +1678,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeFieldResolve hook: Hook error", + "Unexpected error returned from beforeFieldResolve hook: Hook error", }, { name: "beforeOperationExecute (Error is thrown)", @@ -1694,7 +1694,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeOperationExecute hook: Hook error", + "Unexpected error returned from beforeOperationExecute hook (operation: unknown): Hook error", }, { name: "afterFieldResolve (Error is thrown)", @@ -1710,7 +1710,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldResolve hook: Hook error", + "Unexpected error returned from afterFieldResolve hook: Hook error", }, { name: "afterFieldComplete (Error is thrown)", @@ -1726,7 +1726,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in afterFieldComplete hook: Hook error", + "Unexpected error returned from afterFieldComplete hook: Hook error", }, { name: "beforeSubscriptionEventEmit (Error is thrown)", @@ -1745,7 +1745,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + "Unexpected error returned from beforeSubscriptionEventEmit hook (operation: EmitPersons): Hook error", }, { name: "async beforeSubscriptionEventEmit (Error is thrown)", @@ -1766,7 +1766,7 @@ describe.each([ }), }, expectedErrorMessage: - "Unexpected error in beforeSubscriptionEventEmit hook: Hook error", + "Unexpected error returned from beforeSubscriptionEventEmit hook (operation: EmitPersons): Hook error", }, ]; diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index 76249f476..46b8b319a 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -1870,7 +1870,7 @@ function invokeBeforeFieldSubscribeHook( const error = toGraphQLError( rawError, resolveInfo.path, - "Unexpected error in beforeFieldSubscribe hook", + "Unexpected error thrown by beforeFieldSubscribe hook", ); exeContext.errors.push(error); @@ -1879,7 +1879,7 @@ function invokeBeforeFieldSubscribeHook( const error = toGraphQLError( result, resolveInfo.path, - "Unexpected error in beforeFieldSubscribe hook", + "Unexpected error returned from beforeFieldSubscribe hook", ); exeContext.errors.push(error); @@ -1911,7 +1911,7 @@ function invokeBeforeFieldResolveHook( const error = toGraphQLError( rawError, resolveInfo.path, - "Unexpected error in beforeFieldResolve hook", + "Unexpected error thrown by beforeFieldResolve hook", ); exeContext.errors.push(error); @@ -1920,7 +1920,7 @@ function invokeBeforeFieldResolveHook( const error = toGraphQLError( result, resolveInfo.path, - "Unexpected error in beforeFieldResolve hook", + "Unexpected error returned from beforeFieldResolve hook", ); exeContext.errors.push(error); } @@ -1955,7 +1955,7 @@ function invokeAfterFieldResolveHook( const error = toGraphQLError( rawError, resolveInfo.path, - "Unexpected error in afterFieldResolve hook", + "Unexpected error thrown by afterFieldResolve hook", ); exeContext.errors.push(error); @@ -1964,7 +1964,7 @@ function invokeAfterFieldResolveHook( const error = toGraphQLError( result, resolveInfo.path, - "Unexpected error in afterFieldResolve hook", + "Unexpected error returned from afterFieldResolve hook", ); exeContext.errors.push(error); } @@ -1999,7 +1999,7 @@ function invokeAfterFieldSubscribeHook( const error = toGraphQLError( rawError, resolveInfo.path, - "Unexpected error in afterFieldSubscribe hook", + "Unexpected error thrown by afterFieldSubscribe hook", ); exeContext.errors.push(error); @@ -2008,7 +2008,7 @@ function invokeAfterFieldSubscribeHook( const error = toGraphQLError( result, resolveInfo.path, - "Unexpected error in afterFieldSubscribe hook", + "Unexpected error returned from afterFieldSubscribe hook", ); exeContext.errors.push(error); @@ -2045,7 +2045,7 @@ function invokeAfterFieldCompleteHook( const error = toGraphQLError( rawError, resolveInfo.path, - "Unexpected error in afterFieldComplete hook", + "Unexpected error thrown by afterFieldComplete hook", ); exeContext.errors.push(error); @@ -2054,7 +2054,7 @@ function invokeAfterFieldCompleteHook( const error = toGraphQLError( result, resolveInfo.path, - "Unexpected error in afterFieldComplete hook", + "Unexpected error returned from afterFieldComplete hook", ); exeContext.errors.push(error); } @@ -2076,11 +2076,12 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { operation: exeContext.operation, }), (result, rawError) => { + const operationName = exeContext.operation.name?.value ?? "unknown"; if (rawError) { const error = toGraphQLError( rawError, undefined, - "Unexpected error in beforeOperationExecute hook", + `Unexpected error thrown by beforeOperationExecute hook (operation: ${operationName})`, ); exeContext.errors.push(error); @@ -2091,7 +2092,7 @@ function invokeBeforeOperationExecuteHook(exeContext: ExecutionContext) { const error = toGraphQLError( result, undefined, - "Unexpected error in beforeOperationExecute hook", + `Unexpected error returned from beforeOperationExecute hook (operation: ${operationName})`, ); exeContext.errors.push(error); } @@ -2117,11 +2118,12 @@ function invokeBeforeSubscriptionEventEmitHook( eventPayload, }), (result, rawError) => { + const operationName = exeContext.operation.name?.value ?? "unknown"; if (rawError) { const error = toGraphQLError( rawError, undefined, - "Unexpected error in beforeSubscriptionEventEmit hook", + `Unexpected error thrown by beforeSubscriptionEventEmit hook (operation: ${operationName})`, ); exeContext.errors.push(error); @@ -2130,7 +2132,7 @@ function invokeBeforeSubscriptionEventEmitHook( const error = toGraphQLError( result, undefined, - "Unexpected error in beforeSubscriptionEventEmit hook", + `Unexpected error returned from beforeSubscriptionEventEmit hook (operation: ${operationName})`, ); exeContext.errors.push(error); } @@ -2156,11 +2158,12 @@ function invokeAfterBuildResponseHook( result, }), (result, rawError) => { + const operationName = exeContext.operation.name?.value ?? "unknown"; if (rawError) { const error = toGraphQLError( rawError, undefined, - "Unexpected error in afterBuildResponse hook", + `Unexpected error thrown by afterBuildResponse hook (operation: ${operationName})`, ); exeContext.errors.push(error); @@ -2169,7 +2172,7 @@ function invokeAfterBuildResponseHook( const error = toGraphQLError( result, undefined, - "Unexpected error in afterBuildResponse hook", + `Unexpected error returned from afterBuildResponse hook (operation: ${operationName})`, ); exeContext.errors.push(error); From aad215a4ac716976c9c97f2f74688ecb875c70d4 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:25:36 +0100 Subject: [PATCH 18/21] closure removed --- .../supermassive/src/executeWithoutSchema.ts | 167 ++++++++++++------ 1 file changed, 110 insertions(+), 57 deletions(-) diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index 46b8b319a..bba24b888 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -655,6 +655,26 @@ function createSourceEventStream( } } +function afterFieldSubscribeHandle( + resolved: unknown, + isDefaultResolverUsed: boolean, + exeContext: ExecutionContext, + info: ResolveInfo, + hookContext: unknown | undefined, + afterFieldSubscribe: boolean, + error?: Error, +) { + if (!isDefaultResolverUsed && afterFieldSubscribe) { + hookContext = invokeAfterFieldSubscribeHook( + info, + exeContext, + hookContext, + resolved, + error, + ); + } +} + function executeSubscriptionImpl( exeContext: ExecutionContext, ): PromiseOrValue> { @@ -739,33 +759,43 @@ function executeSubscriptionImpl( result = resolveFn(rootValue, args, contextValue, info); } - const afterFieldSubscribeHandle = (resolved: unknown, error?: Error) => { - if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { - hookContext = invokeAfterFieldSubscribeHook( - info, - exeContext, - hookContext, - resolved, - error, - ); - } - }; - if (isPromise(result)) { return result .then(assertEventStream, (error) => { - afterFieldSubscribeHandle(undefined, error); + afterFieldSubscribeHandle( + undefined, + isDefaultResolverUsed, + exeContext, + info, + hookContext, + !!hooks?.afterFieldSubscribe, + error, + ); throw locatedError(error, fieldGroup, pathToArray(path)); }) .then((resolved) => { - afterFieldSubscribeHandle(resolved); + afterFieldSubscribeHandle( + resolved, + isDefaultResolverUsed, + exeContext, + info, + hookContext, + !!hooks?.afterFieldSubscribe, + ); return resolved; }); } const stream = assertEventStream(result); - afterFieldSubscribeHandle(stream); + afterFieldSubscribeHandle( + stream, + isDefaultResolverUsed, + exeContext, + info, + hookContext, + !!hooks?.afterFieldSubscribe, + ); return stream; } catch (error) { if (!isDefaultResolverUsed && hooks?.afterFieldSubscribe) { @@ -1003,30 +1033,21 @@ function resolveAndCompleteField( let completed; - const handleAfterFieldHooks = - ( - hook: - | typeof invokeAfterFieldResolveHook - | typeof invokeAfterFieldCompleteHook, - useHook: boolean, - ) => - (resolved: unknown, error?: Error) => { - if (!isDefaultResolverUsed && useHook) { - hookContext = hook(info, exeContext, hookContext, resolved, error); - return hookContext instanceof GraphQLError ? null : resolved; - } - - return resolved; - }; - if (isPromise(result)) { completed = result - .then( - handleAfterFieldHooks( - invokeAfterFieldResolveHook, - !!hooks?.afterFieldResolve, - ), - ) + .then((resolved) => { + if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { + hookContext = invokeAfterFieldResolveHook( + info, + exeContext, + hookContext, + resolved, + ); + return hookContext instanceof GraphQLError ? null : resolved; + } + + return resolved; + }) .then( (resolved) => { return completeValue( @@ -1042,19 +1063,30 @@ function resolveAndCompleteField( (rawError) => { // That's where afterResolve hook can only be called // in the case of async resolver promise rejection. - handleAfterFieldHooks( - invokeAfterFieldResolveHook, - !!hooks?.afterFieldResolve, - )(undefined, rawError); + if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { + hookContext = invokeAfterFieldResolveHook( + info, + exeContext, + hookContext, + undefined, + rawError, + ); + } // Error will be handled on field completion throw rawError; }, ); } else { - result = handleAfterFieldHooks( - invokeAfterFieldResolveHook, - !!hooks?.afterFieldResolve, - )(result); + if (!isDefaultResolverUsed && hooks?.afterFieldResolve) { + hookContext = invokeAfterFieldResolveHook( + info, + exeContext, + hookContext, + result, + ); + result = hookContext instanceof GraphQLError ? null : result; + } + completed = completeValue( exeContext, returnTypeRef, @@ -1070,17 +1102,31 @@ function resolveAndCompleteField( // Note: we don't rely on a `catch` method, but we do expect "thenable" // to take a second callback for the error case. return completed.then( - handleAfterFieldHooks( - invokeAfterFieldCompleteHook, - !!hooks?.afterFieldComplete, - ), + (resolved) => { + if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { + hookContext = invokeAfterFieldCompleteHook( + info, + exeContext, + hookContext, + resolved, + ); + return hookContext instanceof GraphQLError ? null : resolved; + } + + return resolved; + }, (rawError) => { const error = locatedError(rawError, fieldGroup, pathToArray(path)); - handleAfterFieldHooks( - invokeAfterFieldCompleteHook, - !!hooks?.afterFieldComplete, - )(undefined, error); + if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { + hookContext = invokeAfterFieldCompleteHook( + info, + exeContext, + hookContext, + undefined, + error, + ); + } handleFieldError( rawError, @@ -1095,10 +1141,17 @@ function resolveAndCompleteField( ); } - return handleAfterFieldHooks( - invokeAfterFieldCompleteHook, - !!hooks?.afterFieldComplete, - )(completed); + if (!isDefaultResolverUsed && hooks?.afterFieldComplete) { + hookContext = invokeAfterFieldCompleteHook( + info, + exeContext, + hookContext, + completed, + ); + + return hookContext instanceof GraphQLError ? null : completed; + } + return completed; } catch (rawError) { const pathArray = pathToArray(path); const error = locatedError(rawError, fieldGroup, pathArray); From f779c17f91cfe61b43aa6319ccaf4917876d6294 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:01:37 +0100 Subject: [PATCH 19/21] pre-release config --- .azure-devops/graphitation-release.yml | 1 + ...-supermassive-d7732868-005b-48dd-b592-356b261cde40.json | 7 ------- package.json | 4 ++-- packages/supermassive/package.json | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json diff --git a/.azure-devops/graphitation-release.yml b/.azure-devops/graphitation-release.yml index e5ff2a284..db0dc815e 100644 --- a/.azure-devops/graphitation-release.yml +++ b/.azure-devops/graphitation-release.yml @@ -2,6 +2,7 @@ pr: none trigger: - main - alloy/relay-apollo-duct-tape + - jvejr/supermassive-hooks-error-handling-alpha-release variables: - group: InfoSec-SecurityResults diff --git a/change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json b/change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json deleted file mode 100644 index b07b4f683..000000000 --- a/change/@graphitation-supermassive-d7732868-005b-48dd-b592-356b261cde40.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Hooks error handling", - "packageName": "@graphitation/supermassive", - "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "patch" -} diff --git a/package.json b/package.json index 6d0c7990b..d23283271 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "lint": "lage lint --continue", "lage": "lage", "ci": "yarn lage build types test lint && yarn checkchange", - "beachball": "beachball -b origin/main", + "beachball": "beachball -b origin/jvejr/supermassive-hooks-error-handling-alpha-release", "change": "yarn beachball change", "checkchange": "yarn beachball check", - "release": "yarn beachball publish -t latest", + "release": "yarn beachball publish -t alpha", "postinstall": "patch-package" }, "devDependencies": { diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index 936979128..ef9fee95b 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.7.2", + "version": "3.8.0-alpha.3", "main": "./src/index.ts", "repository": { "type": "git", From 24097ac60df4238a0e6c7fbd549f63384e196770 Mon Sep 17 00:00:00 2001 From: vejrj <77059398+vejrj@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:07:10 +0100 Subject: [PATCH 20/21] Supermassive alpha version bump (#495) * bump --- ...-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json | 7 +++++++ packages/supermassive/src/executeWithoutSchema.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json diff --git a/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json b/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json new file mode 100644 index 000000000..2d7ca161c --- /dev/null +++ b/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Supermassive PR comment fixes", + "packageName": "@graphitation/supermassive", + "email": "77059398+vejrj@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/packages/supermassive/src/executeWithoutSchema.ts b/packages/supermassive/src/executeWithoutSchema.ts index bba24b888..e7a89c178 100644 --- a/packages/supermassive/src/executeWithoutSchema.ts +++ b/packages/supermassive/src/executeWithoutSchema.ts @@ -1110,6 +1110,7 @@ function resolveAndCompleteField( hookContext, resolved, ); + return hookContext instanceof GraphQLError ? null : resolved; } From d83b0a53fa3039f6a4be0f9e5bd804805deaed38 Mon Sep 17 00:00:00 2001 From: Graphitation Service Account Date: Wed, 25 Dec 2024 21:18:59 +0000 Subject: [PATCH 21/21] applying package updates --- ...sive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json | 7 ------- examples/apollo-watch-fragments/package.json | 2 +- examples/supermassive-todomvc/package.json | 2 +- .../CHANGELOG.json | 15 +++++++++++++++ .../package.json | 2 +- .../CHANGELOG.json | 15 +++++++++++++++ .../package.json | 2 +- .../CHANGELOG.json | 15 +++++++++++++++ .../package.json | 2 +- packages/supermassive/CHANGELOG.json | 15 +++++++++++++++ packages/supermassive/CHANGELOG.md | 10 +++++++++- packages/supermassive/package.json | 2 +- packages/webpack-loader/CHANGELOG.json | 15 +++++++++++++++ packages/webpack-loader/package.json | 2 +- 14 files changed, 91 insertions(+), 15 deletions(-) delete mode 100644 change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json diff --git a/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json b/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json deleted file mode 100644 index 2d7ca161c..000000000 --- a/change/@graphitation-supermassive-a373c0e1-4a58-430e-b888-ea21f47afcf6.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "prerelease", - "comment": "Supermassive PR comment fixes", - "packageName": "@graphitation/supermassive", - "email": "77059398+vejrj@users.noreply.github.com", - "dependentChangeType": "none" -} diff --git a/examples/apollo-watch-fragments/package.json b/examples/apollo-watch-fragments/package.json index 1f49f6a88..1949643a6 100644 --- a/examples/apollo-watch-fragments/package.json +++ b/examples/apollo-watch-fragments/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@graphitation/apollo-react-relay-duct-tape-compiler": "^1.6.11", "@graphitation/embedded-document-artefact-loader": "^0.8.5", - "@graphitation/supermassive": "^3.7.2", + "@graphitation/supermassive": "^3.8.0-alpha.4", "@graphql-codegen/cli": "2.2.0", "@graphql-codegen/typescript": "2.2.2", "@graphql-codegen/typescript-resolvers": "^2.2.1", diff --git a/examples/supermassive-todomvc/package.json b/examples/supermassive-todomvc/package.json index 8298220a4..7736ecdf9 100644 --- a/examples/supermassive-todomvc/package.json +++ b/examples/supermassive-todomvc/package.json @@ -18,7 +18,7 @@ "@graphitation/apollo-react-relay-duct-tape": "^1.3.13", "@graphitation/apollo-react-relay-duct-tape-compiler": "^1.6.11", "@graphitation/graphql-js-tag": "^0.9.4", - "@graphitation/supermassive": "^3.7.2", + "@graphitation/supermassive": "^3.8.0-alpha.4", "@graphitation/ts-transform-graphql-js-tag": "^1.4.4", "concurrently": "^6.5.1", "graphql": "^15.6.1", diff --git a/packages/apollo-react-relay-duct-tape-compiler/CHANGELOG.json b/packages/apollo-react-relay-duct-tape-compiler/CHANGELOG.json index d8a490135..29bbe9270 100644 --- a/packages/apollo-react-relay-duct-tape-compiler/CHANGELOG.json +++ b/packages/apollo-react-relay-duct-tape-compiler/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@graphitation/apollo-react-relay-duct-tape-compiler", "entries": [ + { + "date": "Wed, 25 Dec 2024 21:18:59 GMT", + "version": "1.6.11", + "tag": "@graphitation/apollo-react-relay-duct-tape-compiler_v1.6.11", + "comments": { + "none": [ + { + "author": "beachball", + "package": "@graphitation/apollo-react-relay-duct-tape-compiler", + "comment": "Bump @graphitation/supermassive to v3.8.0-alpha.4", + "commit": "not available" + } + ] + } + }, { "date": "Mon, 16 Dec 2024 15:39:40 GMT", "version": "1.6.11", diff --git a/packages/apollo-react-relay-duct-tape-compiler/package.json b/packages/apollo-react-relay-duct-tape-compiler/package.json index e2f7bb10c..d4bbc6514 100644 --- a/packages/apollo-react-relay-duct-tape-compiler/package.json +++ b/packages/apollo-react-relay-duct-tape-compiler/package.json @@ -42,7 +42,7 @@ }, "peerDependencies": { "graphql": "^15.0.0", - "@graphitation/supermassive": "^3.7.2", + "@graphitation/supermassive": "^3.8.0-alpha.4", "typescript": "^5.5.3" }, "publishConfig": { diff --git a/packages/graphql-codegen-supermassive-schema-extraction-plugin/CHANGELOG.json b/packages/graphql-codegen-supermassive-schema-extraction-plugin/CHANGELOG.json index 2d08a494c..7161aee79 100644 --- a/packages/graphql-codegen-supermassive-schema-extraction-plugin/CHANGELOG.json +++ b/packages/graphql-codegen-supermassive-schema-extraction-plugin/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@graphitation/graphql-codegen-supermassive-schema-extraction-plugin", "entries": [ + { + "date": "Wed, 25 Dec 2024 21:18:59 GMT", + "version": "2.0.19", + "tag": "@graphitation/graphql-codegen-supermassive-schema-extraction-plugin_v2.0.19", + "comments": { + "none": [ + { + "author": "beachball", + "package": "@graphitation/graphql-codegen-supermassive-schema-extraction-plugin", + "comment": "Bump @graphitation/supermassive to v3.8.0-alpha.4", + "commit": "not available" + } + ] + } + }, { "date": "Mon, 16 Dec 2024 15:39:40 GMT", "version": "2.0.19", diff --git a/packages/graphql-codegen-supermassive-schema-extraction-plugin/package.json b/packages/graphql-codegen-supermassive-schema-extraction-plugin/package.json index 76c0d4f48..1f2ef5a09 100644 --- a/packages/graphql-codegen-supermassive-schema-extraction-plugin/package.json +++ b/packages/graphql-codegen-supermassive-schema-extraction-plugin/package.json @@ -23,7 +23,7 @@ "@graphql-codegen/plugin-helpers": ">= 1.18.0 < 2" }, "dependencies": { - "@graphitation/supermassive": "^3.7.2", + "@graphitation/supermassive": "^3.8.0-alpha.4", "graphql": "^15.0.0" }, "sideEffects": false, diff --git a/packages/graphql-codegen-supermassive-typed-document-node-plugin/CHANGELOG.json b/packages/graphql-codegen-supermassive-typed-document-node-plugin/CHANGELOG.json index 12da08e13..bcfd3857e 100644 --- a/packages/graphql-codegen-supermassive-typed-document-node-plugin/CHANGELOG.json +++ b/packages/graphql-codegen-supermassive-typed-document-node-plugin/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@graphitation/graphql-codegen-supermassive-typed-document-node-plugin", "entries": [ + { + "date": "Wed, 25 Dec 2024 21:18:59 GMT", + "version": "1.0.12", + "tag": "@graphitation/graphql-codegen-supermassive-typed-document-node-plugin_v1.0.12", + "comments": { + "none": [ + { + "author": "beachball", + "package": "@graphitation/graphql-codegen-supermassive-typed-document-node-plugin", + "comment": "Bump @graphitation/supermassive to v3.8.0-alpha.4", + "commit": "not available" + } + ] + } + }, { "date": "Mon, 16 Dec 2024 15:39:40 GMT", "version": "1.0.12", diff --git a/packages/graphql-codegen-supermassive-typed-document-node-plugin/package.json b/packages/graphql-codegen-supermassive-typed-document-node-plugin/package.json index 3c3d5a23b..400036296 100644 --- a/packages/graphql-codegen-supermassive-typed-document-node-plugin/package.json +++ b/packages/graphql-codegen-supermassive-typed-document-node-plugin/package.json @@ -29,7 +29,7 @@ "@graphql-codegen/visitor-plugin-common": ">= ^1.17.0 < 2", "graphql-tag": ">= 2.11.0 < 3", "@graphql-tools/optimize": "^1.0.1", - "@graphitation/supermassive": "^3.7.2" + "@graphitation/supermassive": "^3.8.0-alpha.4" }, "sideEffects": false, "access": "public", diff --git a/packages/supermassive/CHANGELOG.json b/packages/supermassive/CHANGELOG.json index 66adcac88..8e6e864f0 100644 --- a/packages/supermassive/CHANGELOG.json +++ b/packages/supermassive/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@graphitation/supermassive", "entries": [ + { + "date": "Wed, 25 Dec 2024 21:18:59 GMT", + "version": "3.8.0-alpha.4", + "tag": "@graphitation/supermassive_v3.8.0-alpha.4", + "comments": { + "prerelease": [ + { + "author": "77059398+vejrj@users.noreply.github.com", + "package": "@graphitation/supermassive", + "commit": "24097ac60df4238a0e6c7fbd549f63384e196770", + "comment": "Supermassive PR comment fixes" + } + ] + } + }, { "date": "Mon, 16 Dec 2024 15:39:40 GMT", "version": "3.7.2", diff --git a/packages/supermassive/CHANGELOG.md b/packages/supermassive/CHANGELOG.md index 30d26947d..c9073e876 100644 --- a/packages/supermassive/CHANGELOG.md +++ b/packages/supermassive/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @graphitation/supermassive - + +## 3.8.0-alpha.4 + +Wed, 25 Dec 2024 21:18:59 GMT + +### Changes + +- Supermassive PR comment fixes (77059398+vejrj@users.noreply.github.com) + ## 3.7.2 Mon, 16 Dec 2024 15:39:40 GMT diff --git a/packages/supermassive/package.json b/packages/supermassive/package.json index ef9fee95b..d390a3e99 100644 --- a/packages/supermassive/package.json +++ b/packages/supermassive/package.json @@ -1,7 +1,7 @@ { "name": "@graphitation/supermassive", "license": "MIT", - "version": "3.8.0-alpha.3", + "version": "3.8.0-alpha.4", "main": "./src/index.ts", "repository": { "type": "git", diff --git a/packages/webpack-loader/CHANGELOG.json b/packages/webpack-loader/CHANGELOG.json index ff264c761..3d8097515 100644 --- a/packages/webpack-loader/CHANGELOG.json +++ b/packages/webpack-loader/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@graphitation/webpack-loader", "entries": [ + { + "date": "Wed, 25 Dec 2024 21:18:59 GMT", + "version": "1.0.17", + "tag": "@graphitation/webpack-loader_v1.0.17", + "comments": { + "none": [ + { + "author": "beachball", + "package": "@graphitation/webpack-loader", + "comment": "Bump @graphitation/supermassive to v3.8.0-alpha.4", + "commit": "not available" + } + ] + } + }, { "date": "Mon, 16 Dec 2024 15:39:40 GMT", "version": "1.0.17", diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json index adde05a68..b6269233c 100644 --- a/packages/webpack-loader/package.json +++ b/packages/webpack-loader/package.json @@ -17,7 +17,7 @@ }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", - "@graphitation/supermassive": "^3.7.2" + "@graphitation/supermassive": "^3.8.0-alpha.4" }, "dependencies": { "@graphql-tools/optimize": "^1.1.1",