assistsx-js 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ import type { Step, StepData } from "../step";
2
+ import type { StepFlowData } from "./types";
3
+
4
+ function asFlowData(step: Step): StepFlowData {
5
+ return step.data as StepFlowData;
6
+ }
7
+
8
+ /** 读取业务 payload(不存在时返回空对象) */
9
+ export function getFlowPayload<T extends StepData = StepData>(step: Step): T {
10
+ const data = asFlowData(step);
11
+ const payload = data.payload;
12
+ if (payload !== null && payload !== undefined && typeof payload === "object" && !Array.isArray(payload)) {
13
+ return payload as T;
14
+ }
15
+ return {} as T;
16
+ }
17
+
18
+ /** 浅合并写入 payload */
19
+ export function assignFlowPayload(step: Step, partial: StepData): void {
20
+ const data = asFlowData(step);
21
+ const current = getFlowPayload(step);
22
+ data.payload = { ...current, ...partial };
23
+ }
@@ -0,0 +1,128 @@
1
+ import { Step, type StepImpl } from "../step";
2
+ import { StepError } from "../step-error";
3
+ import type { StepFlowConfig, StepFlowData, StepFlowOutcome } from "./types";
4
+
5
+ function ensureFlowMeta(step: Step, config: StepFlowConfig): void {
6
+ const data = step.data as StepFlowData;
7
+ if (!data.__flow) {
8
+ data.__flow = { id: config.id, state: config.initial };
9
+ }
10
+ }
11
+
12
+ function setFlowState(step: Step, state: string, config: StepFlowConfig): void {
13
+ const data = step.data as StepFlowData;
14
+ if (!data.__flow) {
15
+ data.__flow = { id: config.id, state };
16
+ } else {
17
+ data.__flow.state = state;
18
+ }
19
+ }
20
+
21
+ function resolveOutcome(
22
+ config: StepFlowConfig,
23
+ currentState: string,
24
+ outcome: StepFlowOutcome
25
+ ): string | null | undefined | "legacy" {
26
+ if (outcome.type === "end") {
27
+ return undefined;
28
+ }
29
+ if (outcome.type === "repeat") {
30
+ return null;
31
+ }
32
+ if (outcome.type === "legacy") {
33
+ return "legacy";
34
+ }
35
+ const stateDef = config.states[currentState];
36
+ if (!stateDef) {
37
+ throw new StepError(`StepFlow: unknown state "${currentState}"`, {
38
+ flowId: config.id,
39
+ state: currentState,
40
+ });
41
+ }
42
+ const nextState = stateDef.on[outcome.name];
43
+ if (!nextState) {
44
+ throw new StepError(
45
+ `StepFlow: no transition for event "${outcome.name}" in state "${currentState}"`,
46
+ { flowId: config.id, state: currentState, event: outcome.name }
47
+ );
48
+ }
49
+ return nextState;
50
+ }
51
+
52
+ function createDispatcher(config: StepFlowConfig): StepImpl {
53
+ const dispatcher: StepImpl = async (step) => {
54
+ ensureFlowMeta(step, config);
55
+ const data = step.data as StepFlowData;
56
+ const currentState = data.__flow!.state;
57
+ const stateDef = config.states[currentState];
58
+ if (!stateDef) {
59
+ throw new StepError(`StepFlow: unknown state "${currentState}"`, {
60
+ flowId: config.id,
61
+ state: currentState,
62
+ });
63
+ }
64
+
65
+ const outcome = await stateDef.run(step);
66
+ const resolved = resolveOutcome(config, currentState, outcome);
67
+
68
+ if (resolved === undefined) {
69
+ return undefined;
70
+ }
71
+ if (resolved === null) {
72
+ return step.repeat();
73
+ }
74
+ if (resolved === "legacy") {
75
+ if (outcome.type !== "legacy") {
76
+ throw new StepError("StepFlow: internal legacy outcome mismatch");
77
+ }
78
+ return step.next(outcome.impl);
79
+ }
80
+
81
+ setFlowState(step, resolved, config);
82
+ return step.next(dispatcher);
83
+ };
84
+ return dispatcher;
85
+ }
86
+
87
+ /** 创建流程调度 StepImpl(需先填充 config.states,见 createLaunchState) */
88
+ export function createFlowDispatcher(config: StepFlowConfig): StepImpl {
89
+ return createDispatcher(config);
90
+ }
91
+
92
+ export function buildFlowInitialData(config: StepFlowConfig): StepFlowData {
93
+ return {
94
+ payload: config.data ?? {},
95
+ __flow: {
96
+ id: config.id,
97
+ state: config.initial,
98
+ },
99
+ };
100
+ }
101
+
102
+ export class StepFlow {
103
+ /**
104
+ * 按状态机配置运行流程,内部复用 Step.run 与现有步骤循环。
105
+ */
106
+ static async run(
107
+ config: StepFlowConfig,
108
+ options?: {
109
+ stepId?: string;
110
+ tag?: string;
111
+ delayMs?: number;
112
+ exceptionRetryCountMax?: number;
113
+ }
114
+ ): Promise<Step | undefined> {
115
+ if (!config.states[config.initial]) {
116
+ throw new StepError(
117
+ `StepFlow: initial state "${config.initial}" is not defined`,
118
+ { flowId: config.id }
119
+ );
120
+ }
121
+ const dispatcher = createDispatcher(config);
122
+ return Step.run(dispatcher, {
123
+ ...options,
124
+ tag: options?.tag ?? config.id,
125
+ data: buildFlowInitialData(config),
126
+ });
127
+ }
128
+ }
@@ -0,0 +1,41 @@
1
+ import type { Step, StepData } from "../step";
2
+
3
+ /** 流程步骤执行结果:事件 / 重复 / 结束 / 委托 legacy StepImpl */
4
+ export type StepFlowOutcome =
5
+ | { type: "event"; name: string }
6
+ | { type: "repeat" }
7
+ | { type: "end" }
8
+ | { type: "legacy"; impl: import("../step").StepImpl };
9
+
10
+ /** 流程步骤实现:只描述 UI 与分支事件,不直接决定下一状态 */
11
+ export type FlowStepImpl = (step: Step) => Promise<StepFlowOutcome>;
12
+
13
+ /** 单个流程状态定义 */
14
+ export interface FlowStateDef {
15
+ run: FlowStepImpl;
16
+ /** 事件名 -> 下一状态名 */
17
+ on: Record<string, string>;
18
+ }
19
+
20
+ /** StepFlow 运行配置 */
21
+ export interface StepFlowConfig {
22
+ id?: string;
23
+ initial: string;
24
+ states: Record<string, FlowStateDef>;
25
+ /** 写入 step.data.payload 的初始业务数据 */
26
+ data?: StepData;
27
+ }
28
+
29
+ /** 库写入 step.data.__flow 的结构 */
30
+ export interface StepFlowMeta {
31
+ id?: string;
32
+ state: string;
33
+ }
34
+
35
+ /** step.data 推荐结构(payload 用户读写,__flow 库维护) */
36
+ export interface StepFlowData {
37
+ payload?: StepData;
38
+ __flow?: StepFlowMeta;
39
+ finishMethod?: import("../step").StepImpl;
40
+ [key: string]: unknown;
41
+ }
package/src/step.ts CHANGED
@@ -6,9 +6,14 @@ import { AssistsX } from "./assistsx";
6
6
  import { Node } from "./node";
7
7
  import { CallMethod } from "./call-method";
8
8
  import { useStepStore } from "./step-state-store";
9
+ import { ensureAssistsXPinia } from "./pinia-ensure";
9
10
  import { generateUUID } from "./utils";
10
11
  import { StepError, StepStopError } from "./step-error";
11
12
  import { StepAsync } from "./step-async";
13
+ import type { NodeLookupScope } from "./node-lookup-scope";
14
+
15
+ // 步骤数据类型,始终为普通对象
16
+ export type StepData = Record<string, any>;
12
17
 
13
18
  // 步骤结果类型,可以是Step实例或undefined
14
19
  export type StepResult = Step | undefined;
@@ -26,6 +31,34 @@ export class Step {
26
31
  static showLog: boolean = false;
27
32
  static exceptionRetryCountMaxDefault: number = 3;
28
33
 
34
+ /**
35
+ * 判断步骤数据是否有效(非空且为普通对象)
36
+ */
37
+ private static isValidStepData(data: unknown): data is StepData {
38
+ return (
39
+ data !== null &&
40
+ data !== undefined &&
41
+ typeof data === "object" &&
42
+ !Array.isArray(data)
43
+ );
44
+ }
45
+
46
+ /**
47
+ * 解析步骤数据:优先使用传入值,否则使用当前值,均无效时返回空对象
48
+ */
49
+ private static resolveStepData(
50
+ provided?: StepData,
51
+ fallback?: StepData
52
+ ): StepData {
53
+ if (provided !== undefined && Step.isValidStepData(provided)) {
54
+ return provided;
55
+ }
56
+ if (fallback !== undefined && Step.isValidStepData(fallback)) {
57
+ return fallback;
58
+ }
59
+ return {};
60
+ }
61
+
29
62
  /**
30
63
  * 当前执行步骤的ID
31
64
  */
@@ -59,13 +92,15 @@ export class Step {
59
92
  }: {
60
93
  stepId?: string | undefined;
61
94
  tag?: string | undefined;
62
- data?: any | undefined;
95
+ data?: StepData;
63
96
  delayMs?: number;
64
97
  exceptionRetryCountMax?: number;
65
98
  } = {}
66
99
  ): Promise<Step | undefined> {
67
100
  this.exception = undefined;
101
+ ensureAssistsXPinia();
68
102
  const stepStore = useStepStore();
103
+ const resolvedData = Step.resolveStepData(data);
69
104
  let implnName = impl.name;
70
105
  let currentStep: Step | undefined;
71
106
  let nextStep: Step | undefined;
@@ -83,12 +118,12 @@ export class Step {
83
118
  }
84
119
  }
85
120
 
86
- stepStore.startStep(this._stepId, tag, data);
121
+ stepStore.startStep(this._stepId, tag, resolvedData);
87
122
  currentStep = new Step({
88
123
  stepId: this._stepId,
89
124
  impl,
90
125
  tag,
91
- data,
126
+ data: resolvedData,
92
127
  delayMs,
93
128
  exceptionRetryCountMax,
94
129
  });
@@ -227,7 +262,7 @@ export class Step {
227
262
  const errorMsg = JSON.stringify({
228
263
  impl: implnName,
229
264
  tag: tag,
230
- data: data,
265
+ data: resolvedData,
231
266
  error: e?.message ?? String(e),
232
267
  });
233
268
  stepStore.setError(errorMsg);
@@ -401,7 +436,7 @@ export class Step {
401
436
  /**
402
437
  * 步骤数据
403
438
  */
404
- data: any | undefined;
439
+ data: StepData;
405
440
 
406
441
  /**
407
442
  * 步骤延迟时间(毫秒)
@@ -434,7 +469,7 @@ export class Step {
434
469
  stepId: string;
435
470
  impl: StepImpl | undefined;
436
471
  tag?: string | undefined;
437
- data?: any | undefined;
472
+ data?: StepData;
438
473
  delayMs?: number;
439
474
  repeatCountMax?: number;
440
475
  exceptionRetryCountMax?: number;
@@ -442,7 +477,7 @@ export class Step {
442
477
  }) {
443
478
  this.tag = tag;
444
479
  this.stepId = stepId;
445
- this.data = data ?? {};
480
+ this.data = Step.resolveStepData(data);
446
481
  this.impl = impl;
447
482
  this.delayMs = delayMs;
448
483
  this.repeatCountMax = repeatCountMax;
@@ -471,7 +506,7 @@ export class Step {
471
506
  exceptionRetryCountMax = Step.exceptionRetryCountMaxDefault,
472
507
  }: {
473
508
  tag?: string | undefined;
474
- data?: any | undefined;
509
+ data?: StepData;
475
510
  delayMs?: number;
476
511
  repeatCountMax?: number;
477
512
  exceptionRetryCountMax?: number;
@@ -482,7 +517,7 @@ export class Step {
482
517
  stepId: this.stepId,
483
518
  impl,
484
519
  tag,
485
- data: data ?? this.data ?? {},
520
+ data: Step.resolveStepData(data, this.data),
486
521
  delayMs,
487
522
  repeatCountMax,
488
523
  exceptionRetryCountMax,
@@ -497,7 +532,7 @@ export class Step {
497
532
  exceptionRetryCountMax = Step.exceptionRetryCountMaxDefault,
498
533
  }: {
499
534
  tag?: string | undefined;
500
- data?: any | undefined;
535
+ data?: StepData;
501
536
  delayMs?: number;
502
537
  repeatCountMax?: number;
503
538
  exceptionRetryCountMax?: number;
@@ -508,7 +543,7 @@ export class Step {
508
543
  stepId: this.stepId,
509
544
  impl: undefined,
510
545
  tag,
511
- data: data ?? this.data ?? {},
546
+ data: Step.resolveStepData(data, this.data),
512
547
  delayMs,
513
548
  repeatCountMax,
514
549
  exceptionRetryCountMax,
@@ -527,14 +562,14 @@ export class Step {
527
562
  repeat({
528
563
  stepId = this.stepId,
529
564
  tag = this.tag,
530
- data = this.data ?? {},
565
+ data,
531
566
  delayMs = this.delayMs,
532
567
  repeatCountMax = this.repeatCountMax,
533
568
  exceptionRetryCountMax = this.exceptionRetryCountMax,
534
569
  }: {
535
570
  stepId?: string;
536
571
  tag?: string | undefined;
537
- data?: any | undefined;
572
+ data?: StepData;
538
573
  delayMs?: number;
539
574
  repeatCountMax?: number;
540
575
  exceptionRetryCountMax?: number;
@@ -543,7 +578,7 @@ export class Step {
543
578
  this.repeatCount++;
544
579
  this.stepId = stepId;
545
580
  this.tag = tag;
546
- this.data = data;
581
+ this.data = Step.resolveStepData(data, this.data);
547
582
  this.delayMs = delayMs;
548
583
  this.repeatCountMax = repeatCountMax;
549
584
  this.exceptionRetryCountMax = exceptionRetryCountMax;
@@ -631,11 +666,13 @@ export class Step {
631
666
  filterViewId,
632
667
  filterDes,
633
668
  filterText,
669
+ scope,
634
670
  }: {
635
671
  filterClass?: string;
636
672
  filterViewId?: string;
637
673
  filterDes?: string;
638
674
  filterText?: string;
675
+ scope?: NodeLookupScope;
639
676
  } = {}): Node[] {
640
677
  Step.assert(this.stepId);
641
678
  const nodes = AssistsX.getAllNodes({
@@ -643,6 +680,7 @@ export class Step {
643
680
  filterViewId,
644
681
  filterDes,
645
682
  filterText,
683
+ scope,
646
684
  });
647
685
  Step.assert(this.stepId);
648
686
  Step.assignIdsToNodes(nodes, this.stepId);
@@ -663,11 +701,11 @@ export class Step {
663
701
 
664
702
  /**
665
703
  * 获取当前应用包名
666
- * @returns 包名
704
+ * @param options.scope 节点查找范围(可选)
667
705
  */
668
- public getPackageName(): string {
706
+ public getPackageName(options: { scope?: NodeLookupScope } = {}): string {
669
707
  Step.assert(this.stepId);
670
- const result = AssistsX.getPackageName();
708
+ const result = AssistsX.getPackageName(options);
671
709
  Step.assert(this.stepId);
672
710
  return result;
673
711
  }
@@ -686,10 +724,21 @@ export class Step {
686
724
  filterClass,
687
725
  filterText,
688
726
  filterDes,
689
- }: { filterClass?: string; filterText?: string; filterDes?: string } = {}
727
+ scope,
728
+ }: {
729
+ filterClass?: string;
730
+ filterText?: string;
731
+ filterDes?: string;
732
+ scope?: NodeLookupScope;
733
+ } = {}
690
734
  ): Node[] {
691
735
  Step.assert(this.stepId);
692
- const nodes = AssistsX.findById(id, { filterClass, filterText, filterDes });
736
+ const nodes = AssistsX.findById(id, {
737
+ filterClass,
738
+ filterText,
739
+ filterDes,
740
+ scope,
741
+ });
693
742
  Step.assert(this.stepId);
694
743
  Step.assignIdsToNodes(nodes, this.stepId);
695
744
  return nodes;
@@ -709,13 +758,20 @@ export class Step {
709
758
  filterClass,
710
759
  filterViewId,
711
760
  filterDes,
712
- }: { filterClass?: string; filterViewId?: string; filterDes?: string } = {}
761
+ scope,
762
+ }: {
763
+ filterClass?: string;
764
+ filterViewId?: string;
765
+ filterDes?: string;
766
+ scope?: NodeLookupScope;
767
+ } = {}
713
768
  ): Node[] {
714
769
  Step.assert(this.stepId);
715
770
  const nodes = AssistsX.findByText(text, {
716
771
  filterClass,
717
772
  filterViewId,
718
773
  filterDes,
774
+ scope,
719
775
  });
720
776
  Step.assert(this.stepId);
721
777
  Step.assignIdsToNodes(nodes, this.stepId);
@@ -736,13 +792,20 @@ export class Step {
736
792
  filterText,
737
793
  filterViewId,
738
794
  filterDes,
739
- }: { filterText?: string; filterViewId?: string; filterDes?: string } = {}
795
+ scope,
796
+ }: {
797
+ filterText?: string;
798
+ filterViewId?: string;
799
+ filterDes?: string;
800
+ scope?: NodeLookupScope;
801
+ } = {}
740
802
  ): Node[] {
741
803
  Step.assert(this.stepId);
742
804
  const nodes = AssistsX.findByTags(className, {
743
805
  filterText,
744
806
  filterViewId,
745
807
  filterDes,
808
+ scope,
746
809
  });
747
810
  Step.assert(this.stepId);
748
811
  Step.assignIdsToNodes(nodes, this.stepId);
@@ -752,11 +815,14 @@ export class Step {
752
815
  /**
753
816
  * 查找所有匹配文本的节点
754
817
  * @param text 要查找的文本
755
- * @returns 节点数组
818
+ * @param options.scope 节点查找范围(可选)
756
819
  */
757
- public findByTextAllMatch(text: string): Node[] {
820
+ public findByTextAllMatch(
821
+ text: string,
822
+ { scope }: { scope?: NodeLookupScope } = {}
823
+ ): Node[] {
758
824
  Step.assert(this.stepId);
759
- const nodes = AssistsX.findByTextAllMatch(text);
825
+ const nodes = AssistsX.findByTextAllMatch(text, { scope });
760
826
  Step.assert(this.stepId);
761
827
  Step.assignIdsToNodes(nodes, this.stepId);
762
828
  return nodes;