2026-09-17 12:08:03 +03:00
|
|
|
import type { RudderContext } from '../core/context.js';
|
2026-09-17 15:26:42 +03:00
|
|
|
import { uuidv4 } from '../core/uuid.js';
|
|
|
|
|
import { RudderHttpError, RudderModuleError } from '../client/RudderError.js';
|
|
|
|
|
import type {
|
|
|
|
|
CallModuleFunctionClientResponse,
|
|
|
|
|
CallModuleFunctionRequest,
|
|
|
|
|
ModuleErrorResponse,
|
|
|
|
|
} from '../generated/modules.js';
|
2026-09-17 12:08:03 +03:00
|
|
|
|
2026-09-17 15:26:42 +03:00
|
|
|
export interface ModuleCallOptions {
|
|
|
|
|
/** Version the caller was built against; sent as `X-Rudder-Module-Version: <module>@<version>`. */
|
|
|
|
|
moduleVersion?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ModulesDomain — calls module functions. Usually reached through a client
|
|
|
|
|
* generated by `rudder client generate`, not directly.
|
|
|
|
|
*/
|
2026-09-17 12:08:03 +03:00
|
|
|
export class ModulesDomain {
|
|
|
|
|
constructor(private readonly ctx: RudderContext) {}
|
|
|
|
|
|
2026-09-17 15:26:42 +03:00
|
|
|
/**
|
|
|
|
|
* 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<T = unknown>(
|
|
|
|
|
module: string,
|
|
|
|
|
fn: string,
|
|
|
|
|
args?: unknown,
|
|
|
|
|
options?: ModuleCallOptions,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
const headers: Record<string, string> = { '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<CallModuleFunctionClientResponse>(
|
|
|
|
|
'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<ModuleErrorResponse> = {};
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-09-17 12:08:03 +03:00
|
|
|
}
|
|
|
|
|
}
|