import type { RipplingSDK } from '../../../client'; import { FunctionResponse, type FunctionContext } from '../../../functions'; import type { FunctionExecution, FunctionExecutionsPageCursorURL, ExecutionListLogsResponse, } from '../../../resources/functions/executions'; import type { APIPromise } from '../../../core/api-promise'; import type { PagePromise } from '../../../core/pagination'; import { detachedManifestScope, defineComponent, sourceEntry } from './component'; import { ManifestFunction, type ManifestFunctionProps } from '../manifest-function'; import type { RequestOptions } from '../../../internal/request-options'; import type { ExecutionListParams, ExecutionListLogsParams } from '../../../resources/functions/executions'; /** Parser shared by a function's caller and handler. */ export interface ValueParser { parse(value: unknown): TValue; } /** Authenticated execution resources supplied per call. */ export interface FunctionOperationsContext { readonly functions: { readonly executions: Pick< RipplingSDK['functions']['executions'], 'create' | 'list' | 'retrieve' | 'listLogs' >; }; } export interface DefinedFunctionProps extends ManifestFunctionProps { /** Handler source file read unchanged by project export. Importing the definition performs no file I/O. */ source?: URL; input?: ValueParser; output?: ValueParser; } /** Execution returned by a function with its declared input/output contract. */ export type FunctionExecutionResult = Omit & { inputs: TInput; outputs?: TOutput; }; export interface FunctionExecutions { list( ctx: FunctionOperationsContext, params?: ExecutionListParams, options?: RequestOptions, ): PagePromise; retrieve( ctx: FunctionOperationsContext, executionID: string, options?: RequestOptions, ): APIPromise; listLogs( ctx: FunctionOperationsContext, executionID: string, params?: Omit, options?: RequestOptions, ): APIPromise; } export interface FunctionClient { execute(input: TInput, options?: RequestOptions): Promise>; executions: { list( params?: ExecutionListParams, options?: RequestOptions, ): PagePromise; retrieve(executionID: string, options?: RequestOptions): APIPromise; listLogs( executionID: string, params?: Omit, options?: RequestOptions, ): APIPromise; }; } export function defineFunction( props: DefinedFunctionProps, ): DefinedFunction { return functionComponent.register(new DefinedFunction(props), props); } /** A deployable function reference with the same input/output contract at each call site. */ export class DefinedFunction extends ManifestFunction { private _source: URL | undefined; readonly input: ValueParser | undefined; readonly output: ValueParser | undefined; readonly executions: FunctionExecutions = { list: (ctx: FunctionOperationsContext, params?: ExecutionListParams, options?: RequestOptions) => ctx.functions.executions.list(this.getApiName(), params, options), retrieve: (ctx: FunctionOperationsContext, executionID: string, options?: RequestOptions) => ctx.functions.executions.retrieve(executionID, { function_api_name: this.getApiName() }, options), listLogs: ( ctx: FunctionOperationsContext, executionID: string, params: Omit = {}, options?: RequestOptions, ) => ctx.functions.executions.listLogs( executionID, { ...params, function_api_name: this.getApiName() }, options, ), }; constructor(props: DefinedFunctionProps) { super(detachedManifestScope, props); this.input = props.input; this.output = props.output; if (props.source !== undefined && props.code !== undefined) throw new Error('Define a function with either source or code, not both.'); if (props.source !== undefined && props.source.protocol !== 'file:') throw new Error('Component source must be a file URL.'); this._source = props.source === undefined ? undefined : new URL(props.source.href); } get source(): URL | undefined { return this._source; } /** Replaces inline code or a file-backed source with the supplied code. */ override setCode(code: string): this { super.setCode(code); this._source = undefined; return this; } /** Wraps a handler with the shared contract. Deployed source must be standalone or use declared npm dependencies. */ handler(handler: (input: TInput, context: FunctionContext) => TOutput | Promise) { return async (event: TInput, context: FunctionContext): Promise> => { const result = await handler(this.input == null ? event : this.input.parse(event), context); return new FunctionResponse({ body: this.output == null ? result : this.output.parse(result) }); }; } /** Starts an execution. Asynchronous executions may not have outputs yet. */ async execute( ctx: FunctionOperationsContext, input: TInput, options?: RequestOptions, ): Promise> { const inputs = input; const { outputs, ...execution } = await ctx.functions.executions.create( this.getApiName(), { inputs }, options, ); const body = (outputs as { body: unknown } | null | undefined)?.body; return { ...execution, inputs, ...(outputs != null ? { outputs: this.output == null ? (body as TOutput) : this.output.parse(body) } : {}), }; } } interface FunctionMetadata { type: 'FUNCTION'; api_name: string; name: string; description: string; code_draft?: string; } const functionComponent = defineComponent({ type: 'FUNCTION', define: defineFunction, references: (definition) => () => definition.getReferencedGroups(), serialize: (definition) => sourceEntry(definition.toDict() as FunctionMetadata, definition.source, (config, code_draft) => ({ ...config, code_draft, })), }); /** @internal Forward client helpers to the definition operations. */ export function createFunctionClient( ctx: FunctionOperationsContext, definition: DefinedFunction, ): FunctionClient { return { execute: (input: TInput, options?: RequestOptions) => definition.execute(ctx, input, options), executions: { list: (params?: ExecutionListParams, options?: RequestOptions) => definition.executions.list(ctx, params, options), retrieve: (executionID: string, options?: RequestOptions) => definition.executions.retrieve(ctx, executionID, options), listLogs: ( executionID: string, params?: Omit, options?: RequestOptions, ) => definition.executions.listLogs(ctx, executionID, params, options), }, }; }