Flow Steps
- Client Interaction (Presentation Layer): User interaction occurs in a React component (
page.tsxor optionalcontainer/) within anapps/*application. It prepares data matching the expected input shape (e.g.,CreateUserInput). - Call Server Action (API Entry Point): The component invokes an imported Server Action function (
@core/actions). This function is marked with'use server'. It receives the raw input from the client. - Resolve Validated Service Instance (Action): The Server Action:
- Determines the required service implementation type (e.g., based on context, tenant ID).
- Calls the appropriate Service Factory function (e.g.,
getValidatedUserService(type)from@core/services/[domain]/userServiceFactory.ts). This factory internally uses theserviceMapto get the base implementation and then applies validation wrappers (likecreateServiceMethod) to its methods using the relevant argument schemas (@core/lib/schemas). The factory returns the validated service instance.
- Instantiate & Start Machine (Action): The Action:
- Instantiates (
interpret) the relevant XState machine (@core/machines), providing the resolved and validated service instance via the initialcontext. - Starts the machine interpreter.
- Sends the initial event (e.g.,
{ type: 'CREATE', input: rawClientInput }) to the machine, passing the original input received from the client. Note: Validation happens later when the service method is invoked. - Listens/awaits the machine reaching a final state.
- Instantiates (
- Orchestrate Logic (Machine): The XState machine transitions through its states based on events and its internal context. Its primary role is orchestration.
- Prepare Service Input & Invoke (Machine): Within specific states (often using
invokeservices orentry/exitactions), the machine:- Constructs the appropriate Service Input DTO (defined in
services/[domain]/domain/[Domain]Types.ts) using data from its context (like theinputreceived in the initial event). - Calls the appropriate method on the validated Service implementation (passed via context) using the prepared DTO (e.g.,
context.userService.createUser(serviceInputDto)). This call now triggers the validation wrapper.
- Constructs the appropriate Service Input DTO (defined in
- Validate & Execute (Service Wrapper -> Service Implementation):
- The
createServiceMethodwrapper function executes. - It validates the arguments (containing the Service Input DTO) against its pre-configured Yup schema.
- If validation fails, the wrapper throws a
yup.ValidationError. - If validation succeeds, the wrapper calls the original, unwrapped service implementation method.
- The core service logic executes, potentially throwing its own execution errors.
- The
- Process Service Output/Error & Update Context (Machine): When the invoked service call completes (or throws):
- The machine’s
invokeconfiguration handles the outcome. onDone: If successful, the machine receives the Service Output DTO. It updates its internal context based on the DTO’s structure (e.g., extracting theUserentity).onError: If theinvokecatches an error (eitheryup.ValidationErrorfrom the wrapper or an execution error from the implementation), the machine updates its context with error details (e.g., settingcontext.errorand potentiallycontext.validationErrors).
- The machine’s
- Reach Final State (Machine): The machine eventually transitions to a terminal state (e.g.,
success,failure), indicated bytype: 'final', potentially outputting its final context. - Return Structured Result (Action → Client): The Server Action, having awaited the machine’s final state:
- Inspects the machine’s final state and output/context.
- Formats the outcome into a standard
ActionResultobject (e.g.,{ success: true, data: finalMachineOutput.user }or{ success: false, error: finalMachineOutput.error, validationErrors: finalMachineOutput.validationErrors }). - Returns this
ActionResultto the calling client component.
Clear Roles:
- Client (
apps/*): Renders UI, captures user input, calls Actions, displays results/errors fromActionResult. - Action (
@core/actions): API boundary. Resolves correct validated service, interprets & starts Machine, awaits result, formatsActionResultbased on machine outcome. Initial input validation is now deferred to the service wrapper. - Machine (
@core/machines): Orchestrates flow (XState). Maps client input to Service DTOs, invokes validated Service methods, processes Service output DTOs or catches errors (including validation errors) from theinvoke, manages internal state/context. - Service Factory (
@core/services/[domain]/userServiceFactory.ts): Retrieves base implementation (viaserviceMap), applies validation wrappers (createServiceMethod) to methods, returns the composed, validated service object. - Service Implementation (
@core/services/[domain]/implementations): Executes discrete tasks, contains core logic. Defines contract (Interface + DTOs indomain/). Assumes input is valid by the time its methods are called.
Abbreviated Example: Create User Flow (Action → Machine → Service with Wrapped Validation)
This simplified example demonstrates the core flow: Action resolves the validated service and starts the machine, the machine prepares a DTO and calls the validated service method, the wrapper validates, the implementation performs the work, and the result/error flows back through the machine to the action. 1. Base Types (@core/types)
@core/validation)
@core/services/user/domain)
@core/services/user/implementations)
@core/lib/schemas)
@core/services/user/serviceMap.ts & userServiceFactory.ts)
@core/machines)
@core/actions)
apps/[appName]/app/.../page.tsx)