diff --git a/packages/core/src/destination-kit/action.ts b/packages/core/src/destination-kit/action.ts index 975a3613a97..b9e527176d1 100644 --- a/packages/core/src/destination-kit/action.ts +++ b/packages/core/src/destination-kit/action.ts @@ -16,7 +16,8 @@ import type { DynamicFieldContext, ActionDestinationSuccessResponseType, ActionDestinationErrorResponseType, - ResultMultiStatusNode + ResultMultiStatusNode, + AsyncPollResponseType } from './types' import { syncModeTypes } from './types' import { HTTPError, NormalizedOptions } from '../request-client' @@ -139,6 +140,9 @@ export interface ActionDefinition< /** The operation to perform when this action is triggered for a batch of events */ performBatch?: RequestFn + /** The operation to poll the status of async operation(s) - handles both single and batch operations */ + poll?: RequestFn + /** Hooks are triggered at some point in a mappings lifecycle. They may perform a request with the * destination using the provided inputs and return a response. The response may then optionally be stored * in the mapping for later use in the action. @@ -260,6 +264,7 @@ export class Action readonly hasBatchSupport: boolean readonly hasHookSupport: boolean + readonly hasPollSupport: boolean // Payloads may be any type so we use `any` explicitly here. // eslint-disable-next-line @typescript-eslint/no-explicit-any private extendRequest: RequestExtension | undefined @@ -277,6 +282,7 @@ export class Action + ): Promise { + if (!this.hasPollSupport) { + throw new IntegrationError('This action does not support polling operations.', 'NotImplemented', 501) + } + + // Note: Polling operations typically use data from stateContext rather than transforming the event payload + // Since we're checking the status of async operations, not processing new event data + const payload = {} as Payload + + // Construct the data bundle to send to the poll action + const dataBundle = { + rawData: bundle.data, + rawMapping: bundle.mapping, + settings: bundle.settings, + payload, + auth: bundle.auth, + features: bundle.features, + statsContext: bundle.statsContext, + logger: bundle.logger, + engageDestinationCache: bundle.engageDestinationCache, + transactionContext: bundle.transactionContext, + stateContext: bundle.stateContext, + audienceSettings: bundle.audienceSettings, + subscriptionMetadata: bundle.subscriptionMetadata, + signal: bundle?.signal + } + + // Construct the request client and perform the poll operation + const requestClient = this.createRequestClient(dataBundle) + const pollResponse = await this.definition.poll!(requestClient, dataBundle) + + return pollResponse + } + /* * Extract the dynamic field context and handler path from a field string. Examples: * - "structured.first_name" => { dynamicHandlerPath: "structured.first_name" } @@ -716,6 +758,11 @@ export class Action { return action.executeDynamicField(fieldKey, data, dynamicFn) } + public async executePoll( + actionSlug: string, + { + event, + mapping, + subscriptionMetadata, + settings, + auth, + features, + statsContext, + logger, + engageDestinationCache, + transactionContext, + stateContext, + signal + }: EventInput + ): Promise { + const action = this.actions[actionSlug] + if (!action) { + throw new IntegrationError(`Action ${actionSlug} not found`, 'NotImplemented', 404) + } + + let audienceSettings = {} as AudienceSettings + if (event.context?.personas) { + audienceSettings = event.context?.personas?.audience_settings as AudienceSettings + } + + return action.executePoll({ + mapping, + data: event as unknown as InputData, + settings, + audienceSettings, + auth, + features, + statsContext, + logger, + engageDestinationCache, + transactionContext, + stateContext, + subscriptionMetadata, + signal + }) + } + private async onSubscription( subscription: Subscription, events: SegmentEvent | SegmentEvent[], diff --git a/packages/core/src/destination-kit/types.ts b/packages/core/src/destination-kit/types.ts index e32d84d3634..20abaa7f307 100644 --- a/packages/core/src/destination-kit/types.ts +++ b/packages/core/src/destination-kit/types.ts @@ -398,6 +398,40 @@ export type ActionDestinationErrorResponseType = { body?: JSONLikeObject | string } +export type AsyncActionResponseType = { + /** Indicates this is an async operation */ + isAsync: true + /** Optional message about the async operation(s) */ + message?: string + /** Initial status code */ + status?: number +} + +export type AsyncOperationResult = { + /** The current status of this operation */ + status: 'pending' | 'completed' | 'failed' + /** Message about current state */ + message?: string + /** Final result data when status is 'completed' */ + result?: JSONLikeObject + /** Error information when status is 'failed' */ + error?: { + code: string + message: string + } + /** Original context for this operation */ + context?: JSONLikeObject +} + +export type AsyncPollResponseType = { + /** Array of operation results - single element for individual operations, multiple for batch */ + results: AsyncOperationResult[] + /** Overall status - completed when all operations are done */ + overallStatus: 'pending' | 'completed' | 'failed' | 'partial' + /** Summary message */ + message?: string +} + export type ResultMultiStatusNode = | ActionDestinationSuccessResponseType | (ActionDestinationErrorResponseType & { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d73dd3adfc5..d760f9aa934 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ export { Destination, fieldsToJsonSchema } from './destination-kit' +export type { AsyncActionResponseType, AsyncPollResponseType } from './destination-kit' export { getAuthData } from './destination-kit/parse-settings' export { transform, Features } from './mapping-kit' export {