Explorar o código

refactor(core): Refactor finite state machine types

Michael Bromley %!s(int64=5) %!d(string=hai) anos
pai
achega
355ce1448b
Modificáronse 20 ficheiros con 170 adicións e 70 borrados
  1. 2 2
      packages/core/e2e/order-process.e2e-spec.ts
  2. 1 1
      packages/core/src/api/resolvers/shop/shop-order.resolver.ts
  3. 2 1
      packages/core/src/common/finite-state-machine/finite-state-machine.spec.ts
  4. 2 36
      packages/core/src/common/finite-state-machine/finite-state-machine.ts
  5. 1 1
      packages/core/src/common/finite-state-machine/merge-transition-definitions.spec.ts
  6. 1 1
      packages/core/src/common/finite-state-machine/merge-transition-definitions.ts
  7. 118 0
      packages/core/src/common/finite-state-machine/types.ts
  8. 1 1
      packages/core/src/common/finite-state-machine/validate-transition-definition.spec.ts
  9. 1 1
      packages/core/src/common/finite-state-machine/validate-transition-definition.ts
  10. 1 0
      packages/core/src/common/index.ts
  11. 19 5
      packages/core/src/config/order/custom-order-process.ts
  12. 1 1
      packages/core/src/config/payment-method/payment-method-handler.ts
  13. 2 2
      packages/core/src/config/vendure-config.ts
  14. 7 9
      packages/core/src/service/helpers/order-state-machine/order-state-machine.ts
  15. 1 1
      packages/core/src/service/helpers/order-state-machine/order-state.ts
  16. 3 2
      packages/core/src/service/helpers/payment-state-machine/payment-state-machine.ts
  17. 1 1
      packages/core/src/service/helpers/payment-state-machine/payment-state.ts
  18. 3 2
      packages/core/src/service/helpers/refund-state-machine/refund-state-machine.ts
  19. 1 1
      packages/core/src/service/helpers/refund-state-machine/refund-state.ts
  20. 2 2
      packages/core/src/service/services/order.service.ts

+ 2 - 2
packages/core/e2e/order-process.e2e-spec.ts

@@ -66,7 +66,7 @@ describe('Order process', () => {
         onTransitionEnd(fromState, toState, data) {
             transitionEndSpy(fromState, toState, data);
         },
-        onError(fromState, toState, message) {
+        onTransitionError(fromState, toState, message) {
             transitionErrorSpy(fromState, toState, message);
         },
     };
@@ -84,7 +84,7 @@ describe('Order process', () => {
 
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
-            orderOptions: { process: [customOrderProcess, customOrderProcess2] },
+            orderOptions: { process: [customOrderProcess as any, customOrderProcess2 as any] },
             paymentOptions: {
                 paymentMethodHandlers: [testSuccessfulPaymentMethod],
             },

+ 1 - 1
packages/core/src/api/resolvers/shop/shop-order.resolver.ts

@@ -196,7 +196,7 @@ export class ShopOrderResolver {
 
     @Query()
     @Allow(Permission.Owner)
-    async nextOrderStates(@Ctx() ctx: RequestContext): Promise<string[]> {
+    async nextOrderStates(@Ctx() ctx: RequestContext): Promise<ReadonlyArray<string>> {
         if (ctx.authorizedAsOwnerOnly) {
             const sessionOrder = await this.getOrderFromContext(ctx, true);
             return this.orderService.getNextOrderStates(sessionOrder);

+ 2 - 1
packages/core/src/common/finite-state-machine/finite-state-machine.spec.ts

@@ -1,6 +1,7 @@
 import { of } from 'rxjs';
 
-import { FSM, Transitions } from './finite-state-machine';
+import { FSM } from './finite-state-machine';
+import { Transitions } from './types';
 
 describe('Finite State Machine', () => {
     type TestState = 'DoorsClosed' | 'DoorsOpen' | 'Moving';

+ 2 - 36
packages/core/src/common/finite-state-machine/finite-state-machine.ts

@@ -1,40 +1,6 @@
-import { Observable } from 'rxjs';
-
 import { awaitPromiseOrObservable } from '../utils';
 
-/**
- * @description
- * A type which is used to define all valid transitions and transition callbacks
- *
- * @docsCategory StateMachine
- */
-export type Transitions<State extends string, Target extends string = State> = {
-    [S in State]: {
-        to: Target[];
-        mergeStrategy?: 'merge' | 'replace';
-    };
-};
-
-/**
- * @description
- * The config object used to instantiate a new FSM instance.
- *
- * @docsCategory StateMachine
- */
-export type StateMachineConfig<T extends string, Data = undefined> = {
-    transitions: Transitions<T>;
-    /**
-     * Called before a transition takes place. If the function resolves to false or a string, then the transition
-     * will be cancelled. In the case of a string, the string will be forwarded to the onError handler.
-     */
-    onTransitionStart?(
-        fromState: T,
-        toState: T,
-        data: Data,
-    ): boolean | string | void | Promise<boolean | string | void> | Observable<boolean | string | void>;
-    onTransitionEnd?(fromState: T, toState: T, data: Data): void | Promise<void> | Observable<void>;
-    onError?(fromState: T, toState: T, message?: string): void | Promise<void> | Observable<void>;
-};
+import { StateMachineConfig } from './types';
 
 /**
  * @description
@@ -108,7 +74,7 @@ export class FSM<T extends string, Data = any> {
     /**
      * Returns an array of state to which the machine may transition from the current state.
      */
-    getNextStates(): T[] {
+    getNextStates(): ReadonlyArray<T> {
         return this.config.transitions[this._currentState].to;
     }
 

+ 1 - 1
packages/core/src/common/finite-state-machine/merge-transition-definitions.spec.ts

@@ -1,5 +1,5 @@
-import { Transitions } from './finite-state-machine';
 import { mergeTransitionDefinitions } from './merge-transition-definitions';
+import { Transitions } from './types';
 
 describe('FSM mergeTransitionDefinitions()', () => {
     it('handles no b', () => {

+ 1 - 1
packages/core/src/common/finite-state-machine/merge-transition-definitions.ts

@@ -1,6 +1,6 @@
 import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 
 /**
  * Merges two state machine Transitions definitions.

+ 118 - 0
packages/core/src/common/finite-state-machine/types.ts

@@ -0,0 +1,118 @@
+import { Observable } from 'rxjs';
+
+/**
+ * @description
+ * A type which is used to define valid states and transitions for a state machine based
+ * on {@link FSM}.
+ *
+ * @example
+ * ```TypeScript
+ * type LightColor = 'Green' | 'Amber' | 'Red';
+ *
+ * const trafficLightTransitions: Transitions<LightColor> = {
+ *   Green: {
+ *     to: ['Amber'],
+ *   },
+ *   Amber: {
+ *     to: ['Red'],
+ *   },
+ *   Red: {
+ *     to: ['Green'],
+ *   },
+ * };
+ * ```
+ *
+ * The `mergeStrategy` property defines how to handle the merging of states when one set of
+ * transitions is being merged with another (as in the case of defining a {@link CustomerOrderProcess})
+ *
+ * @docsCategory StateMachine
+ */
+export type Transitions<State extends string, Target extends string = State> = {
+    [S in State]: {
+        to: Readonly<Target[]>;
+        mergeStrategy?: 'merge' | 'replace';
+    };
+};
+
+/**
+ * @description
+ * Called before a transition takes place. If the function resolves to `false` or a string, then the transition
+ * will be cancelled. In the case of a string, the string (error message) will be forwarded to the onError handler.
+ *
+ * If this function returns a value resolving to `true` or `void` (no return value), then the transition
+ * will be permitted.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionStartFn<T extends string, Data> = (
+    fromState: T,
+    toState: T,
+    data: Data,
+) => boolean | string | void | Promise<boolean | string | void> | Observable<boolean | string | void>;
+
+/**
+ * @description
+ * Called after a transition has taken place.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionErrorFn<T extends string> = (
+    fromState: T,
+    toState: T,
+    message?: string,
+) => void | Promise<void> | Observable<void>;
+
+/**
+ * @description
+ * Called when a transition is prevented and the `onTransitionStart` handler has returned an
+ * error message.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionEndFn<T extends string, Data> = (
+    fromState: T,
+    toState: T,
+    data: Data,
+) => void | Promise<void> | Observable<void>;
+
+/**
+ * @description
+ * The config object used to instantiate a new FSM instance.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export interface StateMachineConfig<T extends string, Data = undefined> {
+    /**
+     * @description
+     * Defines the available states of the state machine as well as the permitted
+     * transitions from one state to another.
+     */
+    readonly transitions: Transitions<T>;
+
+    /**
+     * @description
+     * Called before a transition takes place. If the function resolves to `false` or a string, then the transition
+     * will be cancelled. In the case of a string, the string (error message) will be forwarded to the onError handler.
+     *
+     * If this function returns a value resolving to `true` or `void` (no return value), then the transition
+     * will be permitted.
+     */
+    onTransitionStart?: OnTransitionStartFn<T, Data>;
+
+    /**
+     * @description
+     * Called after a transition has taken place.
+     */
+    onTransitionEnd?: OnTransitionEndFn<T, Data>;
+
+    /**
+     * @description
+     * Called when a transition is prevented and the `onTransitionStart` handler has returned an
+     * error message.
+     */
+    onError?: OnTransitionErrorFn<T>;
+}

+ 1 - 1
packages/core/src/common/finite-state-machine/validate-transition-definition.spec.ts

@@ -1,6 +1,6 @@
 import { OrderState } from '../../service/helpers/order-state-machine/order-state';
 
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 import { validateTransitionDefinition } from './validate-transition-definition';
 
 describe('FSM validateTransitionDefinition()', () => {

+ 1 - 1
packages/core/src/common/finite-state-machine/validate-transition-definition.ts

@@ -1,4 +1,4 @@
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 
 type ValidationResult = { reachable: boolean };
 

+ 1 - 0
packages/core/src/common/index.ts

@@ -1,4 +1,5 @@
 export * from './finite-state-machine/finite-state-machine';
+export * from './finite-state-machine/types';
 export * from './async-queue';
 export * from './error/errors';
 export * from './injector';

+ 19 - 5
packages/core/src/config/order/custom-order-process.ts

@@ -1,10 +1,24 @@
-import { StateMachineConfig, Transitions } from '../../common/finite-state-machine/finite-state-machine';
+import {
+    OnTransitionEndFn,
+    OnTransitionErrorFn,
+    OnTransitionStartFn,
+    StateMachineConfig,
+    Transitions,
+} from '../../common/finite-state-machine/types';
 import { InjectableStrategy } from '../../common/types/injectable-strategy';
-import { Order } from '../../entity/order/order.entity';
 import { OrderState, OrderTransitionData } from '../../service/helpers/order-state-machine/order-state';
 
-export interface CustomOrderProcess<State extends string>
-    extends InjectableStrategy,
-        Omit<StateMachineConfig<State & OrderState, OrderTransitionData>, 'transitions'> {
+/**
+ * @description
+ * Used to define extensions to or modifications of the default order process.
+ *
+ * For detailed description of the interface members, see the {@link StateMachineConfig} docs.
+ *
+ * @docsCategory orders
+ */
+export interface CustomOrderProcess<State extends string> extends InjectableStrategy {
     transitions?: Transitions<State, State | OrderState> & Partial<Transitions<OrderState | State>>;
+    onTransitionStart?: OnTransitionStartFn<State | OrderState, OrderTransitionData>;
+    onTransitionEnd?: OnTransitionEndFn<State | OrderState, OrderTransitionData>;
+    onTransitionError?: OnTransitionErrorFn<State | OrderState>;
 }

+ 1 - 1
packages/core/src/config/payment-method/payment-method-handler.ts

@@ -9,7 +9,7 @@ import {
     ConfigurableOperationDefOptions,
     LocalizedStringArray,
 } from '../../common/configurable-operation';
-import { StateMachineConfig } from '../../common/finite-state-machine/finite-state-machine';
+import { StateMachineConfig } from '../../common/finite-state-machine/types';
 import { Order } from '../../entity/order/order.entity';
 import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
 import {

+ 2 - 2
packages/core/src/config/vendure-config.ts

@@ -8,7 +8,7 @@ import { Observable } from 'rxjs';
 import { ConnectionOptions } from 'typeorm';
 
 import { RequestContext } from '../api/common/request-context';
-import { Transitions } from '../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../common/finite-state-machine/types';
 import { Order } from '../entity/order/order.entity';
 import { OrderState } from '../service/helpers/order-state-machine/order-state';
 
@@ -294,7 +294,7 @@ export interface OrderOptions {
      *
      * @default []
      */
-    process?: Array<CustomOrderProcess<string>>;
+    process?: Array<CustomOrderProcess<any>>;
     /**
      * @description
      * Defines the strategy used to merge a guest Order and an existing Order when

+ 7 - 9
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -1,17 +1,13 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { HistoryEntryType } from '@vendure/common/lib/generated-types';
-import { Observable } from 'rxjs';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import {
-    FSM,
-    StateMachineConfig,
-    Transitions,
-} from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
 import { mergeTransitionDefinitions } from '../../../common/finite-state-machine/merge-transition-definitions';
+import { StateMachineConfig, Transitions } from '../../../common/finite-state-machine/types';
 import { validateTransitionDefinition } from '../../../common/finite-state-machine/validate-transition-definition';
 import { awaitPromiseOrObservable } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
@@ -52,7 +48,7 @@ export class OrderStateMachine {
         return new FSM(this.config, currentState).canTransitionTo(newState);
     }
 
-    getNextStates(order: Order): OrderState[] {
+    getNextStates(order: Order): ReadonlyArray<OrderState> {
         const fsm = new FSM(this.config, order.state);
         return fsm.getNextStates();
     }
@@ -164,8 +160,10 @@ export class OrderStateMachine {
             },
             onError: async (fromState, toState, message) => {
                 for (const process of customProcesses) {
-                    if (typeof process.onError === 'function') {
-                        await awaitPromiseOrObservable(process.onError(fromState, toState, message));
+                    if (typeof process.onTransitionError === 'function') {
+                        await awaitPromiseOrObservable(
+                            process.onTransitionError(fromState, toState, message),
+                        );
                     }
                 }
                 throw new IllegalOperationError(message || 'error.cannot-transition-order-from-to', {

+ 1 - 1
packages/core/src/service/helpers/order-state-machine/order-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 
 /**

+ 3 - 2
packages/core/src/service/helpers/payment-state-machine/payment-state-machine.ts

@@ -3,7 +3,8 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import { FSM, StateMachineConfig } from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
+import { StateMachineConfig } from '../../../common/finite-state-machine/types';
 import { ConfigService } from '../../../config/config.service';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
@@ -40,7 +41,7 @@ export class PaymentStateMachine {
 
     constructor(private configService: ConfigService, private historyService: HistoryService) {}
 
-    getNextStates(payment: Payment): PaymentState[] {
+    getNextStates(payment: Payment): ReadonlyArray<PaymentState> {
         const fsm = new FSM(this.config, payment.state);
         return fsm.getNextStates();
     }

+ 1 - 1
packages/core/src/service/helpers/payment-state-machine/payment-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 

+ 3 - 2
packages/core/src/service/helpers/refund-state-machine/refund-state-machine.ts

@@ -3,7 +3,8 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import { FSM, StateMachineConfig } from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
+import { StateMachineConfig } from '../../../common/finite-state-machine/types';
 import { ConfigService } from '../../../config/config.service';
 import { Order } from '../../../entity/order/order.entity';
 import { Refund } from '../../../entity/refund/refund.entity';
@@ -41,7 +42,7 @@ export class RefundStateMachine {
 
     constructor(private configService: ConfigService, private historyService: HistoryService) {}
 
-    getNextStates(refund: Refund): RefundState[] {
+    getNextStates(refund: Refund): ReadonlyArray<RefundState> {
         const fsm = new FSM(this.config, refund.state);
         return fsm.getNextStates();
     }

+ 1 - 1
packages/core/src/service/helpers/refund-state-machine/refund-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 import { Refund } from '../../../entity/refund/refund.entity';

+ 2 - 2
packages/core/src/service/services/order.service.ts

@@ -96,7 +96,7 @@ export class OrderService {
         return Object.entries(this.orderStateMachine.config.transitions).map(([name, { to }]) => ({
             name,
             to,
-        }));
+        })) as OrderProcessState[];
     }
 
     findAll(ctx: RequestContext, options?: ListQueryOptions<Order>): Promise<PaginatedList<Order>> {
@@ -379,7 +379,7 @@ export class OrderService {
         return order.promotions || [];
     }
 
-    getNextOrderStates(order: Order): OrderState[] {
+    getNextOrderStates(order: Order): ReadonlyArray<OrderState> {
         return this.orderStateMachine.getNextStates(order);
     }