From f0208581b0cbb0e1350bb92d6ef3b3800c87bc15 Mon Sep 17 00:00:00 2001 From: Polina Fedorenko Date: Fri, 5 Jun 2026 15:52:21 +0200 Subject: [PATCH 1/2] feat(app-bridge-app): add executeGraphQlWithErrors and deprecate executeGraphQl --- .changeset/lively-otters-wander.md | 19 ++++++++++++ .../src/AppBridgePlatformApp.ts | 1 + .../src/registries/api/ApiMethodRegistry.ts | 8 +++++ .../src/registries/api/ExecuteGraphQl.ts | 6 ++++ .../api/ExecuteGraphQlWithErrors.spec.ts | 23 ++++++++++++++ .../api/ExecuteGraphQlWithErrors.ts | 30 +++++++++++++++++++ .../src/registries/api/index.ts | 1 + .../src/utilities/MessageBus.spec.ts | 22 ++++++++++++++ 8 files changed, 110 insertions(+) create mode 100644 .changeset/lively-otters-wander.md create mode 100644 packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts create mode 100644 packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts diff --git a/.changeset/lively-otters-wander.md b/.changeset/lively-otters-wander.md new file mode 100644 index 000000000..501d23d66 --- /dev/null +++ b/.changeset/lively-otters-wander.md @@ -0,0 +1,19 @@ +--- +"@frontify/app-bridge-app": minor +--- + +feat(api): add `executeGraphQlWithErrors` and deprecate `executeGraphQl` + +`executeGraphQl` resolves only the `data` field of the GraphQL response, so the top-level `errors` array is not accessible to the app. The new `executeGraphQlWithErrors` method resolves the full response envelope, including `errors`. `executeGraphQl` is now deprecated. + +```ts +const response = await appBridge.api({ + name: 'executeGraphQlWithErrors', + payload: { + query: `query CurrentUser { currentUser { id email } }`, + }, +}); + +// response now includes both `data` and any top-level `errors` +const { data, errors } = response; +``` diff --git a/packages/app-bridge-app/src/AppBridgePlatformApp.ts b/packages/app-bridge-app/src/AppBridgePlatformApp.ts index 8f43b3225..096c455e8 100644 --- a/packages/app-bridge-app/src/AppBridgePlatformApp.ts +++ b/packages/app-bridge-app/src/AppBridgePlatformApp.ts @@ -37,6 +37,7 @@ export type PlatformAppApiMethod = PlatformAppApiMethodNameValidator< | 'getSecureRequest' | 'getAccountId' | 'executeGraphQl' + | 'executeGraphQlWithErrors' | 'executeSecureRequest' > >; diff --git a/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts b/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts index f66c8a886..571fbf1f5 100644 --- a/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts +++ b/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts @@ -4,6 +4,10 @@ import { type PlatformAppApiMethodNameValidator } from '../../types/Api.ts'; import { type CreateAssetPayload, type CreateAssetResponse } from './CreateAsset'; import { type ExecuteGraphQlPayload, type ExecuteGraphQlResponse } from './ExecuteGraphQl.ts'; +import { + type ExecuteGraphQlWithErrorsPayload, + type ExecuteGraphQlWithErrorsResponse, +} from './ExecuteGraphQlWithErrors.ts'; import { type ExecuteSecureRequestPayload, type ExecuteSecureRequestResponse } from './ExecuteSecureRequest.ts'; import { type GetAccountIdPayload, type GetAccountIdResponse } from './GetAccountId.ts'; import { @@ -23,5 +27,9 @@ export type ApiMethodRegistry = PlatformAppApiMethodNameValidator<{ getSecureRequest: { payload: GetSecureRequestPayload; response: GetSecureRequestResponse }; getAccountId: { payload: GetAccountIdPayload; response: GetAccountIdResponse }; executeGraphQl: { payload: ExecuteGraphQlPayload; response: ExecuteGraphQlResponse }; + executeGraphQlWithErrors: { + payload: ExecuteGraphQlWithErrorsPayload; + response: ExecuteGraphQlWithErrorsResponse; + }; executeSecureRequest: { payload: ExecuteSecureRequestPayload; response: ExecuteSecureRequestResponse }; }>; diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts index 2ab25ab63..460614cfb 100644 --- a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts @@ -13,6 +13,12 @@ export type ExecuteGraphQlPayload = { export type ExecuteGraphQlResponse = Record; +/** + * @deprecated Use `executeGraphQlWithErrors` instead. `executeGraphQl` resolves only the + * `data` field of the GraphQL response, so the top-level `errors` array is not accessible + * to the consuming app. `executeGraphQlWithErrors` resolves the full response envelope, + * including `errors`. + */ export const executeGraphQl = ( payload: ExecuteGraphQlPayload, ): { name: 'executeGraphQl'; payload: ExecuteGraphQlPayload } => ({ diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts new file mode 100644 index 000000000..b2499d189 --- /dev/null +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts @@ -0,0 +1,23 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { describe, expect, it } from 'vitest'; + +import { executeGraphQlWithErrors } from './ExecuteGraphQlWithErrors'; + +describe('ExecuteGraphQlWithErrors', () => { + it('should return correct method name', () => { + const TEST_QUERY = 'query SomeQuery {}'; + const TEST_VARIABLES = { someVariable: 'someValue' }; + + const graphQlCall = executeGraphQlWithErrors({ + query: TEST_QUERY, + variables: TEST_VARIABLES, + }); + + expect(graphQlCall.name).toBe('executeGraphQlWithErrors'); + expect(graphQlCall.payload).toStrictEqual({ + query: TEST_QUERY, + variables: TEST_VARIABLES, + }); + }); +}); diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts new file mode 100644 index 000000000..8510184a4 --- /dev/null +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts @@ -0,0 +1,30 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type ExecuteGraphQlPayload } from './ExecuteGraphQl.ts'; + +export type ExecuteGraphQlWithErrorsPayload = ExecuteGraphQlPayload; + +export type GraphQlError = { + message: string; + locations?: { line: number; column: number }[]; + path?: (string | number)[]; + extensions?: Record; +}; + +/** + * Unlike `executeGraphQl`, which resolves only the `data` field of the GraphQL response, + * this resolves the full GraphQL response envelope, including the top-level `errors` array + * (and `extensions`, if present). + */ +export type ExecuteGraphQlWithErrorsResponse = { + data?: Record | null; + errors?: GraphQlError[]; + extensions?: Record; +}; + +export const executeGraphQlWithErrors = ( + payload: ExecuteGraphQlWithErrorsPayload, +): { name: 'executeGraphQlWithErrors'; payload: ExecuteGraphQlWithErrorsPayload } => ({ + name: 'executeGraphQlWithErrors', + payload, +}); diff --git a/packages/app-bridge-app/src/registries/api/index.ts b/packages/app-bridge-app/src/registries/api/index.ts index 502aae59d..74c453ee0 100644 --- a/packages/app-bridge-app/src/registries/api/index.ts +++ b/packages/app-bridge-app/src/registries/api/index.ts @@ -6,4 +6,5 @@ export * from './GetAssetResourceInformation'; export * from './GetCurrentUser'; export * from './GetSecureRequest'; export * from './ExecuteGraphQl'; +export * from './ExecuteGraphQlWithErrors'; export * from './ExecuteSecureRequest'; diff --git a/packages/app-bridge-app/src/utilities/MessageBus.spec.ts b/packages/app-bridge-app/src/utilities/MessageBus.spec.ts index 780746085..07fae0b66 100644 --- a/packages/app-bridge-app/src/utilities/MessageBus.spec.ts +++ b/packages/app-bridge-app/src/utilities/MessageBus.spec.ts @@ -144,4 +144,26 @@ describe('MessageBus', () => { expect(result.headers.get('Content-Type')).toBe('application/json'); expect(await result.json()).toEqual({ message: 'test-message' }); }); + + it('should forward the full executeGraphQl response envelope, including top-level errors', async () => { + const channel = new MessageChannel(); + const messageBus = new MessageBus(channel.port1); + + const hostResponse = { + data: { brand: { id: '1' } }, + errors: [{ message: 'Cannot query field "x" on type "Brand".' }], + }; + + channel.port2.onmessage = (event) => { + const { token } = event.data; + channel.port2.postMessage({ message: hostResponse, token }); + }; + + const result = await messageBus.post({ + parameter: { name: 'executeGraphQl', payload: { query: 'query SomeQuery {}' } }, + }); + + expect(result).toEqual(hostResponse); + expect((result as typeof hostResponse).errors).toBeDefined(); + }); }); From 3222027a4a2e496928d2680bbb0f8972d9e0e8bb Mon Sep 17 00:00:00 2001 From: Polina Fedorenko Date: Fri, 5 Jun 2026 16:22:18 +0200 Subject: [PATCH 2/2] refactor(app-bridge-app): renamed executeGraphQlWithErrors to executeGraphQlWithFullResponse --- .changeset/lively-otters-wander.md | 6 +++--- packages/app-bridge-app/src/AppBridgePlatformApp.ts | 2 +- .../src/registries/api/ApiMethodRegistry.ts | 12 ++++++------ .../src/registries/api/ExecuteGraphQl.ts | 4 ++-- ...pec.ts => ExecuteGraphQlWithFullResponse.spec.ts} | 8 ++++---- ...thErrors.ts => ExecuteGraphQlWithFullResponse.ts} | 12 ++++++------ packages/app-bridge-app/src/registries/api/index.ts | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) rename packages/app-bridge-app/src/registries/api/{ExecuteGraphQlWithErrors.spec.ts => ExecuteGraphQlWithFullResponse.spec.ts} (65%) rename packages/app-bridge-app/src/registries/api/{ExecuteGraphQlWithErrors.ts => ExecuteGraphQlWithFullResponse.ts} (64%) diff --git a/.changeset/lively-otters-wander.md b/.changeset/lively-otters-wander.md index 501d23d66..67510bd59 100644 --- a/.changeset/lively-otters-wander.md +++ b/.changeset/lively-otters-wander.md @@ -2,13 +2,13 @@ "@frontify/app-bridge-app": minor --- -feat(api): add `executeGraphQlWithErrors` and deprecate `executeGraphQl` +feat(api): add `executeGraphQlWithFullResponse` and deprecate `executeGraphQl` -`executeGraphQl` resolves only the `data` field of the GraphQL response, so the top-level `errors` array is not accessible to the app. The new `executeGraphQlWithErrors` method resolves the full response envelope, including `errors`. `executeGraphQl` is now deprecated. +`executeGraphQl` resolves only the `data` field of the GraphQL response, so the top-level `errors` array is not accessible to the app. The new `executeGraphQlWithFullResponse` method resolves the full response envelope, including `errors`. `executeGraphQl` is now deprecated. ```ts const response = await appBridge.api({ - name: 'executeGraphQlWithErrors', + name: 'executeGraphQlWithFullResponse', payload: { query: `query CurrentUser { currentUser { id email } }`, }, diff --git a/packages/app-bridge-app/src/AppBridgePlatformApp.ts b/packages/app-bridge-app/src/AppBridgePlatformApp.ts index 096c455e8..4c6ce9d86 100644 --- a/packages/app-bridge-app/src/AppBridgePlatformApp.ts +++ b/packages/app-bridge-app/src/AppBridgePlatformApp.ts @@ -37,7 +37,7 @@ export type PlatformAppApiMethod = PlatformAppApiMethodNameValidator< | 'getSecureRequest' | 'getAccountId' | 'executeGraphQl' - | 'executeGraphQlWithErrors' + | 'executeGraphQlWithFullResponse' | 'executeSecureRequest' > >; diff --git a/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts b/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts index 571fbf1f5..a104defcb 100644 --- a/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts +++ b/packages/app-bridge-app/src/registries/api/ApiMethodRegistry.ts @@ -5,9 +5,9 @@ import { type PlatformAppApiMethodNameValidator } from '../../types/Api.ts'; import { type CreateAssetPayload, type CreateAssetResponse } from './CreateAsset'; import { type ExecuteGraphQlPayload, type ExecuteGraphQlResponse } from './ExecuteGraphQl.ts'; import { - type ExecuteGraphQlWithErrorsPayload, - type ExecuteGraphQlWithErrorsResponse, -} from './ExecuteGraphQlWithErrors.ts'; + type ExecuteGraphQlWithFullResponse, + type ExecuteGraphQlWithFullResponsePayload, +} from './ExecuteGraphQlWithFullResponse.ts'; import { type ExecuteSecureRequestPayload, type ExecuteSecureRequestResponse } from './ExecuteSecureRequest.ts'; import { type GetAccountIdPayload, type GetAccountIdResponse } from './GetAccountId.ts'; import { @@ -27,9 +27,9 @@ export type ApiMethodRegistry = PlatformAppApiMethodNameValidator<{ getSecureRequest: { payload: GetSecureRequestPayload; response: GetSecureRequestResponse }; getAccountId: { payload: GetAccountIdPayload; response: GetAccountIdResponse }; executeGraphQl: { payload: ExecuteGraphQlPayload; response: ExecuteGraphQlResponse }; - executeGraphQlWithErrors: { - payload: ExecuteGraphQlWithErrorsPayload; - response: ExecuteGraphQlWithErrorsResponse; + executeGraphQlWithFullResponse: { + payload: ExecuteGraphQlWithFullResponsePayload; + response: ExecuteGraphQlWithFullResponse; }; executeSecureRequest: { payload: ExecuteSecureRequestPayload; response: ExecuteSecureRequestResponse }; }>; diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts index 460614cfb..e55f9a28a 100644 --- a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts @@ -14,9 +14,9 @@ export type ExecuteGraphQlPayload = { export type ExecuteGraphQlResponse = Record; /** - * @deprecated Use `executeGraphQlWithErrors` instead. `executeGraphQl` resolves only the + * @deprecated Use `executeGraphQlWithFullResponse` instead. `executeGraphQl` resolves only the * `data` field of the GraphQL response, so the top-level `errors` array is not accessible - * to the consuming app. `executeGraphQlWithErrors` resolves the full response envelope, + * to the consuming app. `executeGraphQlWithFullResponse` resolves the full response envelope, * including `errors`. */ export const executeGraphQl = ( diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.spec.ts similarity index 65% rename from packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts rename to packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.spec.ts index b2499d189..f839b4ef9 100644 --- a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.spec.ts +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.spec.ts @@ -2,19 +2,19 @@ import { describe, expect, it } from 'vitest'; -import { executeGraphQlWithErrors } from './ExecuteGraphQlWithErrors'; +import { executeGraphQlWithFullResponse } from './ExecuteGraphQlWithFullResponse'; -describe('ExecuteGraphQlWithErrors', () => { +describe('ExecuteGraphQlWithFullResponse', () => { it('should return correct method name', () => { const TEST_QUERY = 'query SomeQuery {}'; const TEST_VARIABLES = { someVariable: 'someValue' }; - const graphQlCall = executeGraphQlWithErrors({ + const graphQlCall = executeGraphQlWithFullResponse({ query: TEST_QUERY, variables: TEST_VARIABLES, }); - expect(graphQlCall.name).toBe('executeGraphQlWithErrors'); + expect(graphQlCall.name).toBe('executeGraphQlWithFullResponse'); expect(graphQlCall.payload).toStrictEqual({ query: TEST_QUERY, variables: TEST_VARIABLES, diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.ts similarity index 64% rename from packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts rename to packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.ts index 8510184a4..58e31dcbb 100644 --- a/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithErrors.ts +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQlWithFullResponse.ts @@ -2,7 +2,7 @@ import { type ExecuteGraphQlPayload } from './ExecuteGraphQl.ts'; -export type ExecuteGraphQlWithErrorsPayload = ExecuteGraphQlPayload; +export type ExecuteGraphQlWithFullResponsePayload = ExecuteGraphQlPayload; export type GraphQlError = { message: string; @@ -16,15 +16,15 @@ export type GraphQlError = { * this resolves the full GraphQL response envelope, including the top-level `errors` array * (and `extensions`, if present). */ -export type ExecuteGraphQlWithErrorsResponse = { +export type ExecuteGraphQlWithFullResponse = { data?: Record | null; errors?: GraphQlError[]; extensions?: Record; }; -export const executeGraphQlWithErrors = ( - payload: ExecuteGraphQlWithErrorsPayload, -): { name: 'executeGraphQlWithErrors'; payload: ExecuteGraphQlWithErrorsPayload } => ({ - name: 'executeGraphQlWithErrors', +export const executeGraphQlWithFullResponse = ( + payload: ExecuteGraphQlWithFullResponsePayload, +): { name: 'executeGraphQlWithFullResponse'; payload: ExecuteGraphQlWithFullResponsePayload } => ({ + name: 'executeGraphQlWithFullResponse', payload, }); diff --git a/packages/app-bridge-app/src/registries/api/index.ts b/packages/app-bridge-app/src/registries/api/index.ts index 74c453ee0..497ee7c29 100644 --- a/packages/app-bridge-app/src/registries/api/index.ts +++ b/packages/app-bridge-app/src/registries/api/index.ts @@ -6,5 +6,5 @@ export * from './GetAssetResourceInformation'; export * from './GetCurrentUser'; export * from './GetSecureRequest'; export * from './ExecuteGraphQl'; -export * from './ExecuteGraphQlWithErrors'; +export * from './ExecuteGraphQlWithFullResponse'; export * from './ExecuteSecureRequest';