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
TypeScript
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?: {
[]: ActionFunction<TContext, InferServiceContract<TServiceContracts>['events'], InferServiceContract<TServiceContracts>['events'], TActions[K], never, ToParameterizedObject<TActions>, ToParameterizedObject<TGuards>, never, InferServiceContract<TServiceContracts>['emitted']>;
};
guards?: {
[]: (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, {} | {
[]: {} | any | {
[]: {} | any | any;
};
} | {
[]: {} | 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>>;
};