Skip to main content

Flow Steps

  1. Client Interaction (Presentation Layer): User interaction occurs in a React component (page.tsx or optional container/) within an apps/* application. It prepares data matching the expected input shape (e.g., CreateUserInput).
  2. 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.
  3. 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 the serviceMap to get the base implementation and then applies validation wrappers (like createServiceMethod) to its methods using the relevant argument schemas (@core/lib/schemas). The factory returns the validated service instance.
  4. Instantiate & Start Machine (Action): The Action:
    • Instantiates (interpret) the relevant XState machine (@core/machines), providing the resolved and validated service instance via the initial context.
    • 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.
  5. Orchestrate Logic (Machine): The XState machine transitions through its states based on events and its internal context. Its primary role is orchestration.
  6. Prepare Service Input & Invoke (Machine): Within specific states (often using invoke services or entry/exit actions), the machine:
    • Constructs the appropriate Service Input DTO (defined in services/[domain]/domain/[Domain]Types.ts) using data from its context (like the input received 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.
  7. Validate & Execute (Service Wrapper -> Service Implementation):
    • The createServiceMethod wrapper 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.
  8. Process Service Output/Error & Update Context (Machine): When the invoked service call completes (or throws):
    • The machine’s invoke configuration 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 the User entity).
    • onError: If the invoke catches an error (either yup.ValidationError from the wrapper or an execution error from the implementation), the machine updates its context with error details (e.g., setting context.error and potentially context.validationErrors).
  9. Reach Final State (Machine): The machine eventually transitions to a terminal state (e.g., success, failure), indicated by type: 'final', potentially outputting its final context.
  10. 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 ActionResult object (e.g., { success: true, data: finalMachineOutput.user } or { success: false, error: finalMachineOutput.error, validationErrors: finalMachineOutput.validationErrors }).
    • Returns this ActionResult to the calling client component.
Clear Roles:
  • Client (apps/*): Renders UI, captures user input, calls Actions, displays results/errors from ActionResult.
  • Action (@core/actions): API boundary. Resolves correct validated service, interprets & starts Machine, awaits result, formats ActionResult based 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 the invoke, manages internal state/context.
  • Service Factory (@core/services/[domain]/userServiceFactory.ts): Retrieves base implementation (via serviceMap), 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 in domain/). 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)
2. Client Input Validation Schema (@core/validation)
3. Service Definition (@core/services/user/domain)
4. Service Implementation (@core/services/user/implementations)
5. Service Argument Validation Schema (@core/lib/schemas)
6. Service Resolution (@core/services/user/serviceMap.ts & userServiceFactory.ts)
7. XState Machine (@core/machines)
8. Server Action (@core/actions)
9. UI Page (apps/[appName]/app/.../page.tsx)