UNPKG

arvo-event-handler

Version:

Type-safe event handler system with versioning, telemetry, and contract validation for distributed Arvo event-driven architectures, featuring routing and multi-handler support.

209 lines (208 loc) 9.19 kB
import { type ArvoOrchestratorEventTypeGen, type InferVersionedArvoContract, type VersionedArvoContract } from 'arvo-core'; import { type ActionFunction, type MachineConfig, type MachineContext, type MetaObject, type ParameterizedObject, type SetupTypes } from 'xstate'; import type { z } from 'zod'; import ArvoMachine from '.'; import type { EnqueueArvoEventActionParam, ExtractOrchestratorType, InferServiceContract, ToParameterizedObject, ToProvidedActor } from './types'; /** * Establishes the foundation for creating Arvo-compatible state machines. * * This function configures the core elements of an Arvo state machine, including * built-in actions like `enqueueArvoEvent`, and enforces Arvo-specific constraints * to ensure compatibility with the Arvo event-driven system. * * @param param - Configuration object for the machine setup * @returns An object containing the `createMachine` function * @throws {Error} If 'actors', 'delays', or reserved action names are used in the configuration * * @description * `setupArvoMachine` is a crucial function in the Arvo ecosystem, designed to create * synchronous state machine orchestrations for Arvo's event-driven architecture. * It builds upon XState, providing a tailored implementation that: * * 1. Enforces synchronous behavior to maintain predictable state transitions * 3. Implements Arvo-specific constraints and features * * Key features: * - Synchronous execution: Ensures deterministic behavior in event-driven systems * - Built-in actions: Includes `enqueueArvoEvent` for Arvo event handling * - Constraint checking: Prevents usage of asynchronous features like 'actors' or 'delays' * * @remarks * While `setupArvoMachine` is based on XState's `setup` and `createMachine` functions, * it includes Arvo-specific modifications and restrictions. For a deeper understanding * of the underlying XState concepts, refer to the official XState documentation: * - XState setup: https://stately.ai/docs/setup * - XState createMachine: https://stately.ai/docs/machines * * @example * Here's a comprehensive example demonstrating how to use `setupArvoMachine`: * * ```typescript * import { setupArvoMachine } from 'arvo-xstate' * import { createArvoOrchestratorContract, ArvoErrorSchema, createArvoContract } from 'arvo-core' * import { z } from 'zod' * * // Define the LLM orchestrator contract * const llmContract = createArvoOrchestratorContract({ * uri: `#/orchestrators/llm/`, * type: 'llm', * versions: { * '0.0.1': { * init: z.object({ * request: z.string(), * llm: z.enum(['gpt-4', 'gpt-4o']), * }), * complete: z.object({ * response: z.string(), * }) * } * } * }) * * // Define the OpenAI service contract * const openAiContract = createArvoContract({ * uri: `#/services/openai`, * type: 'com.openai.completions', * versions: { * '0.0.1': { * accepts: z.object({ * request: z.string() * }), * emits: { * 'evt.openai.completions.success': z.object({ * response: z.string(), * }) * } * } * } * }) * * const machineId = 'machineV100' * * // Set up the Arvo machine * const llmMachine = setupArvoMachine({ * contracts: { * self: llmContract.version('0.0.1'), * services: { * openAiContract.version('0.0.1'), * } * }, * types: { * context: {} as { * request: string, * llm: string, * response: string | null, * errors: z.infer<typeof ArvoErrorSchema>[] * }, * tags: {} as 'pending' | 'success' | 'error', * }, * actions: { * log: ({context, event}) => console.log({context, event}) * }, * guards: { * isValid: ({context, event}) => Boolean(context.request) * } * }).createMachine({ * id: machineId, * context: ({input}) => ({ * request: input.request, * llm: input.llm, * response: null, * errors: [], * }), * initial: 'validate', * states: { * validate: { * always: [ * { * guard: 'isValid', * target: 'llm', * }, * { * target: 'error', * } * ] * }, * llm: { * entry: [ * { * type: 'log', * }, * emit(({context}) => ({ * type: 'com.openai.completions', * data: { * request: context.request, * }, * })) * ], * on: { * 'evt.openai.completions.success': { * actions: [ * assign({response: ({event}) => event.response}) * ], * target: 'done' * }, * 'sys.com.openai.completions.error': { * actions: [ * assign({errors: ({context, event}) => [...context.errors, event.body]}) * ], * target: 'error' * } * } * }, * done: { * type: 'final' * }, * error: { * type: 'final' * }, * } * }); * ``` * * This example demonstrates: * 1. Defining Arvo contracts for the orchestrator and a service * 2. Setting up an Arvo machine with contracts, types, actions, and guards * 3. Creating a machine with states for validation, LLM interaction, and error handling * 4. Using XState features like `emit` bound with Arvo contracts for event emitting and event handling via transitions */ export declare function setupArvoMachine<TContext extends MachineContext, TSelfContract extends VersionedArvoContract<any, any>, TServiceContracts extends Record<string, VersionedArvoContract<any, any>>, TActions extends Record<string, ParameterizedObject['params'] | undefined> = {}, TGuards extends Record<string, ParameterizedObject['params'] | undefined> = {}, TTag extends string = string, TMeta extends MetaObject = MetaObject>(param: { schemas?: unknown; contracts: { self: TSelfContract; services: TServiceContracts; }; types?: Omit<SetupTypes<TContext, InferServiceContract<TServiceContracts>['events'], {}, TTag, InferVersionedArvoContract<TSelfContract>['accepts']['data'], InferVersionedArvoContract<TSelfContract>['emits'][ReturnType<typeof ArvoOrchestratorEventTypeGen.complete<ExtractOrchestratorType<TSelfContract['accepts']['type']>>>]['data'], InferServiceContract<TServiceContracts>['emitted'], TMeta>, 'input' | 'output' | 'children' | 'emitted'>; actions?: { [K in keyof TActions]: ActionFunction<TContext, InferServiceContract<TServiceContracts>['events'], InferServiceContract<TServiceContracts>['events'], TActions[K], never, ToParameterizedObject<TActions>, ToParameterizedObject<TGuards>, never, InferServiceContract<TServiceContracts>['emitted']>; }; guards?: { [K in keyof TGuards]: (args: { context: TContext; event: InferServiceContract<TServiceContracts>['events']; }, params: TGuards[K]) => boolean; }; }): { createMachine: <const TConfig extends MachineConfig<TContext, InferServiceContract<TServiceContracts>["events"], ToProvidedActor<{}, {}>, ToParameterizedObject<TActions & { enqueueArvoEvent: EnqueueArvoEventActionParam; }>, ToParameterizedObject<TGuards>, never, TTag, InferVersionedArvoContract<TSelfContract>["accepts"], z.input<TSelfContract["emits"][ReturnType<typeof ArvoOrchestratorEventTypeGen.complete<ExtractOrchestratorType<TSelfContract["accepts"]["type"]>>>]>, InferServiceContract<TServiceContracts>["emitted"], TMeta>>(config: TConfig & { id: string; version?: TSelfContract["version"]; }) => ArvoMachine<string, TSelfContract["version"], TSelfContract, TServiceContracts, import("xstate").StateMachine<TContext, { [K in keyof TServiceContracts]: import("./types").InferEmittableEventsFromVersionedArvoContract<TServiceContracts[K]>; }[keyof TServiceContracts], {}, never, import("xstate").IsNever<TActions & { enqueueArvoEvent: EnqueueArvoEventActionParam; }> extends true ? never : import("xstate").Values<{ [K_1 in (keyof TActions & string) | "enqueueArvoEvent"]: { type: K_1; params: (TActions & { enqueueArvoEvent: EnqueueArvoEventActionParam; })[K_1]; }; }>, import("xstate").IsNever<TGuards> extends true ? never : import("xstate").Values<{ [K_2 in keyof TGuards & string]: { type: K_2; params: TGuards[K_2]; }; }>, never, {} | { [x: string]: {} | any | { [x: string]: {} | any | any; }; } | { [x: string]: {} | any | any; }, TTag, TSelfContract["accepts"]["schema"]["_output"], { [K_3 in string & keyof TSelfContract["emits"]]: import("arvo-core").InferArvoEvent<import("arvo-core").ArvoEvent<TSelfContract["emits"][K_3]["_output"], Record<string, any>, K_3>>; }[`arvo.orc.${ExtractOrchestratorType<TSelfContract["accepts"]["type"]>}.done`]["data"], { [K_4 in keyof TServiceContracts]: EnqueueArvoEventActionParam<z.input<TServiceContracts[K_4]["accepts"]["schema"]>, TServiceContracts[K_4]["accepts"]["type"], Record<string, string | number | boolean | null>>; }[keyof TServiceContracts], TMeta, any>>; };