From 929592a5a0426cb5fe170d0a838fdb6df185c8b3 Mon Sep 17 00:00:00 2001 From: Scott Kennedy Date: Fri, 17 Apr 2026 15:50:52 -0400 Subject: [PATCH 1/5] Copy apps-function-query runtime into apps plugin proxy-codegen folder --- .../execute-backend-function.test.ts | 126 ++++++++++++++++++ .../proxy-codegen/execute-backend-function.ts | 57 ++++++++ .../transports/dev-server-transport.ts | 79 +++++++++++ .../post-message-transport.ts | 102 ++++++++++++++ .../post-message-transport/types.ts | 35 +++++ .../apps/src/vite/proxy-codegen/types.ts | 48 +++++++ 6 files changed, 447 insertions(+) create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts create mode 100644 packages/plugins/apps/src/vite/proxy-codegen/types.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts b/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts new file mode 100644 index 000000000..be1504dde --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts @@ -0,0 +1,126 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +// Only the fetch (dev-server) transport is exercised here. The iframe +// postMessage transport requires a DOM (`window`, `MessageEvent`) that this +// repo's node-only jest harness doesn't provide — adding jsdom collides with +// the shared `setupAfterEnv.ts` (nock → TextEncoder). postMessage coverage +// lives with the original tests in web-ui's @datadog/apps-function-query +// until a DOM-enabled harness is introduced. + +import { executeBackendFunction } from './execute-backend-function'; +import { BackendFunctionError } from './types'; + +describe('executeBackendFunction', () => { + let originalFetch: typeof fetch; + + beforeEach(() => { + originalFetch = global.fetch; + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it('should successfully execute a backend function', async () => { + const mockResponse = { success: true, result: { data: { sum: 12 } } }; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await executeBackendFunction<{ sum: number }>('testWithImport', [5, 7]); + + expect(result).toEqual({ sum: 12 }); + expect(global.fetch).toHaveBeenCalledWith('/__dd/executeAction', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + functionName: 'testWithImport', + args: [5, 7], + }), + }); + }); + + it('should throw BackendFunctionError on network error', async () => { + global.fetch = jest.fn().mockRejectedValue(new Error('Network failed')); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + BackendFunctionError, + ); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + 'Network error while executing backend function "testFunction"', + ); + }); + + it('should throw BackendFunctionError on non-ok response', async () => { + global.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 500, + text: async () => 'Internal Server Error', + }); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + BackendFunctionError, + ); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + 'Backend function "testFunction" failed with status 500', + ); + }); + + it('should throw BackendFunctionError when response contains error field', async () => { + const mockResponse = { + error: 'Function execution failed', + data: null, + }; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + BackendFunctionError, + ); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + 'Backend function "testFunction" returned an error', + ); + }); + + it('should throw BackendFunctionError on invalid JSON response', async () => { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + json: async () => { + throw new Error('Invalid JSON'); + }, + }); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + BackendFunctionError, + ); + + await expect(executeBackendFunction('testFunction', [])).rejects.toThrow( + 'Failed to parse response from backend function', + ); + }); + + it('should include statusCode in error when available', async () => { + global.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 404, + text: async () => 'Not Found', + }); + + await expect(executeBackendFunction('testFunction', [])).rejects.toMatchObject({ + name: 'BackendFunctionError', + functionName: 'testFunction', + statusCode: 404, + }); + }); +}); diff --git a/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts b/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts new file mode 100644 index 000000000..464e2a120 --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts @@ -0,0 +1,57 @@ +// eslint-disable-next-line spaced-comment +/// + +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +/* eslint-env browser */ + +import { devServerTransport } from './transports/dev-server-transport'; +import { postMessageTransport } from './transports/post-message-transport/post-message-transport'; +import type { BackendFunctionTransport } from './types'; + +function isInIframe(): boolean { + try { + return typeof window !== 'undefined' && window.parent !== window; + } catch { + // Accessing window.parent can throw if cross-origin + return true; + } +} + +function resolveTransport(): BackendFunctionTransport { + if (isInIframe()) { + return postMessageTransport; + } + return devServerTransport; +} + +/** + * Executes a backend function by name with the provided arguments. + * + * When running inside an iframe embedded in App Builder, automatically + * uses postMessage to communicate with the parent window. Otherwise, + * uses HTTP fetch to the backend endpoint. + * + * @param functionName - The name of the backend function to execute + * @param args - Array of arguments to pass to the function + * @returns Promise that resolves to the function's return value + * @throws {BackendFunctionError} If the request fails or the function throws an error + * + * @example + * ```typescript + * const result = await executeBackendFunction<{ sum: number }, [number, number]>( + * 'testWithImport', + * [5, 7] + * ); + * console.log(result.sum); // 12 + * ``` + */ +export async function executeBackendFunction( + functionName: string, + args: TArgs, +): Promise { + const transport = resolveTransport(); + return transport(functionName, args); +} diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts b/packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts new file mode 100644 index 000000000..44f1bcf07 --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts @@ -0,0 +1,79 @@ +// eslint-disable-next-line spaced-comment +/// + +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +import type { + BackendFunctionTransport, + ExecuteActionRequest, + ExecuteActionResponse, +} from '../types'; +import { BackendFunctionError } from '../types'; + +const ENDPOINT = '/__dd/executeAction'; + +export const devServerTransport: BackendFunctionTransport = async ( + functionName: string, + args: unknown[], +): Promise => { + const request: ExecuteActionRequest = { + functionName, + args, + }; + + let response: Response; + try { + response = await fetch(ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + }); + } catch (error) { + throw new BackendFunctionError( + `Network error while executing backend function "${functionName}": ${ + error instanceof Error ? error.message : String(error) + }`, + functionName, + ); + } + + if (!response.ok) { + let errorMessage = `Backend function "${functionName}" failed with status ${response.status}`; + try { + const errorBody = await response.text(); + if (errorBody) { + errorMessage += `: ${errorBody}`; + } + } catch { + // Ignore errors reading error body + } + throw new BackendFunctionError(errorMessage, functionName, response.status); + } + + let executeActionResponse: ExecuteActionResponse; + try { + executeActionResponse = await response.json(); + } catch (error) { + throw new BackendFunctionError( + `Failed to parse response from backend function "${functionName}": ${ + error instanceof Error ? error.message : String(error) + }`, + functionName, + response.status, + ); + } + + if (!executeActionResponse.success) { + throw new BackendFunctionError( + `Backend function "${functionName}" returned an error: ${executeActionResponse.error}`, + functionName, + response.status, + ); + } + + return executeActionResponse.result.data; +}; diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts b/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts new file mode 100644 index 000000000..aa17bb52f --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts @@ -0,0 +1,102 @@ +// eslint-disable-next-line spaced-comment +/// + +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +/* eslint-env browser */ + +import type { BackendFunctionTransport } from '../../types'; +import { BackendFunctionError } from '../../types'; + +import type { IframeQueryResponse } from './types'; + +const POSTMESSAGE_TIMEOUT_MS = 120_000; + +let requestCounter = 0; + +function generateRequestId(): string { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + requestCounter += 1; + return `req-${Date.now()}-${requestCounter}`; +} + +function isQueryResponse(data: unknown, requestId: string): data is IframeQueryResponse { + return ( + data !== null && + typeof data === 'object' && + 'type' in data && + data.type === 'app-builder:run-query:response' && + 'requestId' in data && + data.requestId === requestId + ); +} + +/** + * Transport for executing backend functions via `postMessage` when the app + * is hosted inside an iframe (e.g. App Builder preview). Sends a + * `app-builder:run-query` message to the parent window and listens for a + * matching `app-builder:run-query:response` reply. Rejects if no response + * arrives within {@link POSTMESSAGE_TIMEOUT_MS}. + */ +export const postMessageTransport: BackendFunctionTransport = ( + functionName: string, + args: unknown[], +): Promise => { + const requestId = generateRequestId(); + + return new Promise((resolve, reject) => { + let timeoutId: ReturnType; + + function cleanup(): void { + window.removeEventListener('message', handleMessage); + clearTimeout(timeoutId); + } + + function handleMessage(event: MessageEvent): void { + if (!isQueryResponse(event.data, requestId)) { + return; + } + + cleanup(); + + const response = event.data as IframeQueryResponse; + + if (response.success) { + resolve(response.result.data); + } else { + reject( + new BackendFunctionError( + response.error ?? `Backend function "${functionName}" failed`, + functionName, + ), + ); + } + } + + window.addEventListener('message', handleMessage); + + timeoutId = setTimeout(() => { + cleanup(); + reject( + new BackendFunctionError( + `Backend function "${functionName}" timed out waiting for response`, + functionName, + ), + ); + }, POSTMESSAGE_TIMEOUT_MS); + + window.parent.postMessage( + { + type: 'app-builder:run-query', + requestId, + queryName: functionName, + args, + }, + '*', + ); + }); +}; diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts b/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts new file mode 100644 index 000000000..05aa44670 --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts @@ -0,0 +1,35 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +import type { ExecuteActionResponse } from '../../types'; + +// Request: iframe → parent +export type IframeQueryRequest = { + type: 'app-builder:run-query'; + requestId: string; + queryName: string; + args?: unknown[]; + templateParams?: Record; +}; + +export type IframeQueryPing = { + type: 'app-builder:ping'; + requestId: string; +}; + +export type IframeToParentMessage = IframeQueryRequest | IframeQueryPing; + +// Response: parent → iframe +export type IframeQueryResponse = { + type: 'app-builder:run-query:response'; + requestId: string; +} & ExecuteActionResponse; + +export type IframeQueryPong = { + type: 'app-builder:pong'; + requestId: string; + availableQueries: string[]; +}; + +export type ParentToIframeMessage = IframeQueryResponse | IframeQueryPong; diff --git a/packages/plugins/apps/src/vite/proxy-codegen/types.ts b/packages/plugins/apps/src/vite/proxy-codegen/types.ts new file mode 100644 index 000000000..907be1c2d --- /dev/null +++ b/packages/plugins/apps/src/vite/proxy-codegen/types.ts @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +/** + * Request payload for executing a backend function + */ +export interface ExecuteActionRequest { + functionName: string; + args: unknown[]; +} + +/** + * Response from executing a backend function + */ +export type ExecuteActionResponse = + | { + success: true; + result: { + data: TData; + }; + } + | { + success: false; + error: string; + }; + +/** + * A transport function that executes a backend function via a specific mechanism. + */ +export type BackendFunctionTransport = ( + functionName: string, + args: unknown[], +) => Promise; + +/** + * Error thrown when backend function execution fails + */ +export class BackendFunctionError extends Error { + constructor( + message: string, + public functionName: string, + public statusCode?: number, + ) { + super(message); + this.name = 'BackendFunctionError'; + } +} From 41540bfc4eee9da7411fbcec5fa80a247c6322c7 Mon Sep 17 00:00:00 2001 From: Scott Kennedy Date: Sat, 18 Apr 2026 09:50:19 -0400 Subject: [PATCH 2/5] Move apps-function-query runtime into backend/client --- .../client}/execute-backend-function.test.ts | 0 .../proxy-codegen => backend/client}/execute-backend-function.ts | 0 .../client}/transports/dev-server-transport.ts | 0 .../transports/post-message-transport/post-message-transport.ts | 0 .../client}/transports/post-message-transport/types.ts | 0 .../apps/src/{vite/proxy-codegen => backend/client}/types.ts | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/execute-backend-function.test.ts (100%) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/execute-backend-function.ts (100%) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/transports/dev-server-transport.ts (100%) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/transports/post-message-transport/post-message-transport.ts (100%) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/transports/post-message-transport/types.ts (100%) rename packages/plugins/apps/src/{vite/proxy-codegen => backend/client}/types.ts (100%) diff --git a/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts b/packages/plugins/apps/src/backend/client/execute-backend-function.test.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.test.ts rename to packages/plugins/apps/src/backend/client/execute-backend-function.test.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts b/packages/plugins/apps/src/backend/client/execute-backend-function.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/execute-backend-function.ts rename to packages/plugins/apps/src/backend/client/execute-backend-function.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts b/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/transports/dev-server-transport.ts rename to packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts b/packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/post-message-transport.ts rename to packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts b/packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/transports/post-message-transport/types.ts rename to packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts diff --git a/packages/plugins/apps/src/vite/proxy-codegen/types.ts b/packages/plugins/apps/src/backend/client/types.ts similarity index 100% rename from packages/plugins/apps/src/vite/proxy-codegen/types.ts rename to packages/plugins/apps/src/backend/client/types.ts From ac028e824da1778c01cc1a4f65e7fd6b1ffed495 Mon Sep 17 00:00:00 2001 From: Scott Kennedy Date: Sat, 18 Apr 2026 10:38:43 -0400 Subject: [PATCH 3/5] Isolate DOM types in apps plugin with separate tsconfig.client.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend/client files use browser APIs (window, MessageEvent, fetch) that need DOM types, but adding them via /// polluted the entire workspace compilation — causing Node.js types (Blob, ReadableStream, Headers) to clash with DOM equivalents across @dd/core and other packages. Fix: split into two tsconfigs — the main one excludes backend/client, and a new tsconfig.client.json includes only those files with lib: ["es2022", "dom"]. The typecheck script chains both. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/plugins/apps/package.json | 2 +- .../src/backend/client/execute-backend-function.ts | 3 --- .../client/transports/dev-server-transport.ts | 3 --- .../post-message-transport/post-message-transport.ts | 3 --- packages/plugins/apps/tsconfig.client.json | 12 ++++++++++++ packages/plugins/apps/tsconfig.json | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 packages/plugins/apps/tsconfig.client.json diff --git a/packages/plugins/apps/package.json b/packages/plugins/apps/package.json index 02cc9c462..542d44cb7 100644 --- a/packages/plugins/apps/package.json +++ b/packages/plugins/apps/package.json @@ -19,7 +19,7 @@ "./*": "./src/*.ts" }, "scripts": { - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.client.json" }, "dependencies": { "@dd/core": "workspace:*", diff --git a/packages/plugins/apps/src/backend/client/execute-backend-function.ts b/packages/plugins/apps/src/backend/client/execute-backend-function.ts index 464e2a120..666f1da81 100644 --- a/packages/plugins/apps/src/backend/client/execute-backend-function.ts +++ b/packages/plugins/apps/src/backend/client/execute-backend-function.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line spaced-comment -/// - // Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. diff --git a/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts b/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts index 44f1bcf07..93085541d 100644 --- a/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts +++ b/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line spaced-comment -/// - // Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. diff --git a/packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts b/packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts index aa17bb52f..f08666659 100644 --- a/packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts +++ b/packages/plugins/apps/src/backend/client/transports/post-message-transport/post-message-transport.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line spaced-comment -/// - // Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. diff --git a/packages/plugins/apps/tsconfig.client.json b/packages/plugins/apps/tsconfig.client.json new file mode 100644 index 000000000..c7102ac8b --- /dev/null +++ b/packages/plugins/apps/tsconfig.client.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "rootDir": "./", + "outDir": "./dist", + "lib": ["es2022", "dom"] + }, + "files": [], + "include": ["src/backend/client/**/*"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/plugins/apps/tsconfig.json b/packages/plugins/apps/tsconfig.json index 6c1d3065e..5d847c7c2 100644 --- a/packages/plugins/apps/tsconfig.json +++ b/packages/plugins/apps/tsconfig.json @@ -6,5 +6,5 @@ "outDir": "./dist" }, "include": ["**/*"], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules", "src/backend/client"] } \ No newline at end of file From d972daf59d52a8d897e4e4810a692c3eb1d19b08 Mon Sep 17 00:00:00 2001 From: Scott Kennedy Date: Sat, 18 Apr 2026 14:09:56 -0400 Subject: [PATCH 4/5] Add project reference from apps tsconfig to client tsconfig for VSCode DOM lib routing --- packages/plugins/apps/tsconfig.client.json | 4 +++- packages/plugins/apps/tsconfig.json | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/plugins/apps/tsconfig.client.json b/packages/plugins/apps/tsconfig.client.json index c7102ac8b..0d4ffd136 100644 --- a/packages/plugins/apps/tsconfig.client.json +++ b/packages/plugins/apps/tsconfig.client.json @@ -4,7 +4,9 @@ "baseUrl": "./", "rootDir": "./", "outDir": "./dist", - "lib": ["es2022", "dom"] + "lib": ["es2022", "dom"], + "composite": true, + "noEmit": false }, "files": [], "include": ["src/backend/client/**/*"], diff --git a/packages/plugins/apps/tsconfig.json b/packages/plugins/apps/tsconfig.json index 5d847c7c2..8c66ff72d 100644 --- a/packages/plugins/apps/tsconfig.json +++ b/packages/plugins/apps/tsconfig.json @@ -6,5 +6,6 @@ "outDir": "./dist" }, "include": ["**/*"], - "exclude": ["dist", "node_modules", "src/backend/client"] + "exclude": ["dist", "node_modules", "src/backend/client"], + "references": [{ "path": "./tsconfig.client.json" }] } \ No newline at end of file From 05977d5968a4007265f8c8ef7c1da39b8f6e01a9 Mon Sep 17 00:00:00 2001 From: Scott Kennedy Date: Tue, 21 Apr 2026 12:57:21 -0400 Subject: [PATCH 5/5] Extract ExecuteAction wire protocol types into backend/protocol.ts --- .../client/transports/dev-server-transport.ts | 7 +--- .../post-message-transport/types.ts | 2 +- .../plugins/apps/src/backend/client/types.ts | 23 ----------- packages/plugins/apps/src/backend/protocol.ts | 38 +++++++++++++++++++ packages/plugins/apps/tsconfig.client.json | 2 +- 5 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 packages/plugins/apps/src/backend/protocol.ts diff --git a/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts b/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts index 93085541d..3ed187233 100644 --- a/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts +++ b/packages/plugins/apps/src/backend/client/transports/dev-server-transport.ts @@ -2,11 +2,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. -import type { - BackendFunctionTransport, - ExecuteActionRequest, - ExecuteActionResponse, -} from '../types'; +import type { ExecuteActionRequest, ExecuteActionResponse } from '../../protocol'; +import type { BackendFunctionTransport } from '../types'; import { BackendFunctionError } from '../types'; const ENDPOINT = '/__dd/executeAction'; diff --git a/packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts b/packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts index 05aa44670..658c8ec99 100644 --- a/packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts +++ b/packages/plugins/apps/src/backend/client/transports/post-message-transport/types.ts @@ -2,7 +2,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. -import type { ExecuteActionResponse } from '../../types'; +import type { ExecuteActionResponse } from '../../../protocol'; // Request: iframe → parent export type IframeQueryRequest = { diff --git a/packages/plugins/apps/src/backend/client/types.ts b/packages/plugins/apps/src/backend/client/types.ts index 907be1c2d..d3b58a982 100644 --- a/packages/plugins/apps/src/backend/client/types.ts +++ b/packages/plugins/apps/src/backend/client/types.ts @@ -2,29 +2,6 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. -/** - * Request payload for executing a backend function - */ -export interface ExecuteActionRequest { - functionName: string; - args: unknown[]; -} - -/** - * Response from executing a backend function - */ -export type ExecuteActionResponse = - | { - success: true; - result: { - data: TData; - }; - } - | { - success: false; - error: string; - }; - /** * A transport function that executes a backend function via a specific mechanism. */ diff --git a/packages/plugins/apps/src/backend/protocol.ts b/packages/plugins/apps/src/backend/protocol.ts new file mode 100644 index 000000000..04e8751fa --- /dev/null +++ b/packages/plugins/apps/src/backend/protocol.ts @@ -0,0 +1,38 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +/** + * Wire protocol for backend function execution shared by the dev server, + * the dev-server transport, and the iframe postMessage transport. + * + * Lives at the backend/ root (above client/) so server-side and client-side + * code can both depend on it without one importing through the other. + */ + +/** + * Request payload for executing a backend function. + */ +export interface ExecuteActionRequest { + functionName: string; + args: unknown[]; +} + +/** + * Response from executing a backend function. + * + * The `data` wrapper inside `result` mirrors the Datadog app-builder + * `preview-async` queries API contract: a JS action's return value is + * surfaced as `outputs: { data: }`. Both transports unwrap `.data`. + */ +export type ExecuteActionResponse = + | { + success: true; + result: { + data: TData; + }; + } + | { + success: false; + error: string; + }; diff --git a/packages/plugins/apps/tsconfig.client.json b/packages/plugins/apps/tsconfig.client.json index 0d4ffd136..ca2406e65 100644 --- a/packages/plugins/apps/tsconfig.client.json +++ b/packages/plugins/apps/tsconfig.client.json @@ -8,7 +8,7 @@ "composite": true, "noEmit": false }, - "files": [], + "files": ["src/backend/protocol.ts"], "include": ["src/backend/client/**/*"], "exclude": ["dist", "node_modules"] }