import type { RudderContext } from '../core/context.js'; import { uuidv4 } from '../core/uuid.js'; import { RudderHttpError, RudderModuleError } from '../client/RudderError.js'; import type { CallModuleFunctionClientResponse, CallModuleFunctionRequest, ModuleErrorResponse, } from '../generated/modules.js'; export interface ModuleCallOptions { /** Version the caller was built against; sent as `X-Rudder-Module-Version: @`. */ moduleVersion?: string; } /** * ModulesDomain — calls module functions. Usually reached through a client * generated by `rudder client generate`, not directly. */ export class ModulesDomain { constructor(private readonly ctx: RudderContext) {} /** * Calls `module.fn` and returns its `result`. Each call gets one * Idempotency-Key reused across retries (network errors, 5xx, 409 `conflict`). * Rejects with {@link RudderModuleError} on 422. */ async call( module: string, fn: string, args?: unknown, options?: ModuleCallOptions, ): Promise { const headers: Record = { 'Idempotency-Key': uuidv4() }; if (options?.moduleVersion) { headers['X-Rudder-Module-Version'] = `${module}@${options.moduleVersion}`; } const path = `/sdk/v1/modules/${encodeURIComponent(module)}/${encodeURIComponent(fn)}`; try { const response = await this.ctx.request( 'POST', path, { args: args ?? {} } satisfies CallModuleFunctionRequest, { headers, retry: true }, ); return response?.result as T; } catch (err) { if (err instanceof RudderHttpError && err.status === 422 && err.code) { let payload: Partial = {}; try { payload = JSON.parse(err.body); } catch { // err.code came from this body, so it is JSON; keep the fallback anyway. } throw new RudderModuleError(err.statusText, err.body, { ...payload, code: err.code }); } throw err; } } }