@objectstack/service-automation 3.0.7
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.
- package/.turbo/turbo-build.log +22 -0
- package/LICENSE +202 -0
- package/dist/index.cjs +381 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +350 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
- package/src/engine.test.ts +608 -0
- package/src/engine.ts +257 -0
- package/src/index.ts +14 -0
- package/src/plugin.ts +80 -0
- package/src/plugins/crud-nodes-plugin.ts +68 -0
- package/src/plugins/http-connector-plugin.ts +70 -0
- package/src/plugins/logic-nodes-plugin.ts +78 -0
- package/tsconfig.json +9 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { FlowNodeParsed } from '@objectstack/spec/automation';
|
|
2
|
+
import { IAutomationService, Logger, AutomationContext, AutomationResult } from '@objectstack/spec/contracts';
|
|
3
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Each node type corresponds to a NodeExecutor.
|
|
7
|
+
* Third-party plugins only need to implement this interface and register
|
|
8
|
+
* it with the engine to extend automation capabilities.
|
|
9
|
+
*/
|
|
10
|
+
interface NodeExecutor {
|
|
11
|
+
/** Corresponds to FlowNodeAction enum value */
|
|
12
|
+
readonly type: string;
|
|
13
|
+
/**
|
|
14
|
+
* Execute a node
|
|
15
|
+
* @param node - Current node definition
|
|
16
|
+
* @param variables - Flow variable context (read/write)
|
|
17
|
+
* @param context - Trigger context
|
|
18
|
+
* @returns Execution result (may include output data, branch conditions, etc.)
|
|
19
|
+
*/
|
|
20
|
+
execute(node: FlowNodeParsed, variables: Map<string, unknown>, context: AutomationContext): Promise<NodeExecutionResult>;
|
|
21
|
+
}
|
|
22
|
+
interface NodeExecutionResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
output?: Record<string, unknown>;
|
|
25
|
+
error?: string;
|
|
26
|
+
/** Used by decision nodes — returns the selected branch label */
|
|
27
|
+
branchLabel?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Trigger interface. Schedule/Event/API triggers are registered via plugins.
|
|
31
|
+
*/
|
|
32
|
+
interface FlowTrigger {
|
|
33
|
+
readonly type: string;
|
|
34
|
+
start(flowName: string, callback: (ctx: AutomationContext) => Promise<void>): void;
|
|
35
|
+
stop(flowName: string): void;
|
|
36
|
+
}
|
|
37
|
+
declare class AutomationEngine implements IAutomationService {
|
|
38
|
+
private flows;
|
|
39
|
+
private nodeExecutors;
|
|
40
|
+
private triggers;
|
|
41
|
+
private logger;
|
|
42
|
+
constructor(logger: Logger);
|
|
43
|
+
/** Register a node executor (called by plugins) */
|
|
44
|
+
registerNodeExecutor(executor: NodeExecutor): void;
|
|
45
|
+
/** Unregister a node executor (hot-unplug) */
|
|
46
|
+
unregisterNodeExecutor(type: string): void;
|
|
47
|
+
/** Register a trigger (called by plugins) */
|
|
48
|
+
registerTrigger(trigger: FlowTrigger): void;
|
|
49
|
+
/** Unregister a trigger (hot-unplug) */
|
|
50
|
+
unregisterTrigger(type: string): void;
|
|
51
|
+
/** Get all registered node types */
|
|
52
|
+
getRegisteredNodeTypes(): string[];
|
|
53
|
+
/** Get all registered trigger types */
|
|
54
|
+
getRegisteredTriggerTypes(): string[];
|
|
55
|
+
registerFlow(name: string, definition: unknown): void;
|
|
56
|
+
unregisterFlow(name: string): void;
|
|
57
|
+
listFlows(): Promise<string[]>;
|
|
58
|
+
execute(flowName: string, context?: AutomationContext): Promise<AutomationResult>;
|
|
59
|
+
private executeNode;
|
|
60
|
+
private evaluateCondition;
|
|
61
|
+
private retryExecution;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuration options for the AutomationServicePlugin.
|
|
66
|
+
*/
|
|
67
|
+
interface AutomationServicePluginOptions {
|
|
68
|
+
/** Enable debug logging for flow execution */
|
|
69
|
+
debug?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* AutomationServicePlugin — Core engine plugin
|
|
73
|
+
*
|
|
74
|
+
* Responsibilities:
|
|
75
|
+
* 1. init phase: Create engine instance, register as 'automation' service
|
|
76
|
+
* 2. start phase: Trigger 'automation:ready' hook for node plugin registration
|
|
77
|
+
* 3. destroy phase: Clean up resources
|
|
78
|
+
*
|
|
79
|
+
* Does NOT implement any specific nodes — nodes are registered by other plugins
|
|
80
|
+
* via the engine's extension API.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { LiteKernel } from '@objectstack/core';
|
|
85
|
+
* import { AutomationServicePlugin } from '@objectstack/service-automation';
|
|
86
|
+
*
|
|
87
|
+
* const kernel = new LiteKernel();
|
|
88
|
+
* kernel.use(new AutomationServicePlugin());
|
|
89
|
+
* await kernel.bootstrap();
|
|
90
|
+
*
|
|
91
|
+
* const automation = kernel.getService('automation');
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare class AutomationServicePlugin implements Plugin {
|
|
95
|
+
name: string;
|
|
96
|
+
version: string;
|
|
97
|
+
type: "standard";
|
|
98
|
+
dependencies: string[];
|
|
99
|
+
private engine?;
|
|
100
|
+
private readonly options;
|
|
101
|
+
constructor(options?: AutomationServicePluginOptions);
|
|
102
|
+
init(ctx: PluginContext): Promise<void>;
|
|
103
|
+
start(ctx: PluginContext): Promise<void>;
|
|
104
|
+
destroy(): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* CRUD Node Plugin — Provides get_record / create_record / update_record / delete_record
|
|
109
|
+
*
|
|
110
|
+
* Dependencies: service-automation (engine)
|
|
111
|
+
*
|
|
112
|
+
* In a full runtime environment these nodes would delegate to ObjectQL (data layer).
|
|
113
|
+
* This MVP implementation provides the extension point structure.
|
|
114
|
+
*/
|
|
115
|
+
declare class CrudNodesPlugin implements Plugin {
|
|
116
|
+
name: string;
|
|
117
|
+
version: string;
|
|
118
|
+
type: "standard";
|
|
119
|
+
dependencies: string[];
|
|
120
|
+
init(ctx: PluginContext): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Logic Node Plugin — Provides decision / assignment / loop nodes
|
|
125
|
+
*
|
|
126
|
+
* Dependencies: service-automation (engine)
|
|
127
|
+
*/
|
|
128
|
+
declare class LogicNodesPlugin implements Plugin {
|
|
129
|
+
name: string;
|
|
130
|
+
version: string;
|
|
131
|
+
type: "standard";
|
|
132
|
+
dependencies: string[];
|
|
133
|
+
init(ctx: PluginContext): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* HTTP + Connector Node Plugin — Provides http_request / connector_action nodes
|
|
138
|
+
*
|
|
139
|
+
* Dependencies: service-automation (engine)
|
|
140
|
+
*/
|
|
141
|
+
declare class HttpConnectorPlugin implements Plugin {
|
|
142
|
+
name: string;
|
|
143
|
+
version: string;
|
|
144
|
+
type: "standard";
|
|
145
|
+
dependencies: string[];
|
|
146
|
+
init(ctx: PluginContext): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { AutomationEngine, AutomationServicePlugin, type AutomationServicePluginOptions, CrudNodesPlugin, type FlowTrigger, HttpConnectorPlugin, LogicNodesPlugin, type NodeExecutionResult, type NodeExecutor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { FlowNodeParsed } from '@objectstack/spec/automation';
|
|
2
|
+
import { IAutomationService, Logger, AutomationContext, AutomationResult } from '@objectstack/spec/contracts';
|
|
3
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Each node type corresponds to a NodeExecutor.
|
|
7
|
+
* Third-party plugins only need to implement this interface and register
|
|
8
|
+
* it with the engine to extend automation capabilities.
|
|
9
|
+
*/
|
|
10
|
+
interface NodeExecutor {
|
|
11
|
+
/** Corresponds to FlowNodeAction enum value */
|
|
12
|
+
readonly type: string;
|
|
13
|
+
/**
|
|
14
|
+
* Execute a node
|
|
15
|
+
* @param node - Current node definition
|
|
16
|
+
* @param variables - Flow variable context (read/write)
|
|
17
|
+
* @param context - Trigger context
|
|
18
|
+
* @returns Execution result (may include output data, branch conditions, etc.)
|
|
19
|
+
*/
|
|
20
|
+
execute(node: FlowNodeParsed, variables: Map<string, unknown>, context: AutomationContext): Promise<NodeExecutionResult>;
|
|
21
|
+
}
|
|
22
|
+
interface NodeExecutionResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
output?: Record<string, unknown>;
|
|
25
|
+
error?: string;
|
|
26
|
+
/** Used by decision nodes — returns the selected branch label */
|
|
27
|
+
branchLabel?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Trigger interface. Schedule/Event/API triggers are registered via plugins.
|
|
31
|
+
*/
|
|
32
|
+
interface FlowTrigger {
|
|
33
|
+
readonly type: string;
|
|
34
|
+
start(flowName: string, callback: (ctx: AutomationContext) => Promise<void>): void;
|
|
35
|
+
stop(flowName: string): void;
|
|
36
|
+
}
|
|
37
|
+
declare class AutomationEngine implements IAutomationService {
|
|
38
|
+
private flows;
|
|
39
|
+
private nodeExecutors;
|
|
40
|
+
private triggers;
|
|
41
|
+
private logger;
|
|
42
|
+
constructor(logger: Logger);
|
|
43
|
+
/** Register a node executor (called by plugins) */
|
|
44
|
+
registerNodeExecutor(executor: NodeExecutor): void;
|
|
45
|
+
/** Unregister a node executor (hot-unplug) */
|
|
46
|
+
unregisterNodeExecutor(type: string): void;
|
|
47
|
+
/** Register a trigger (called by plugins) */
|
|
48
|
+
registerTrigger(trigger: FlowTrigger): void;
|
|
49
|
+
/** Unregister a trigger (hot-unplug) */
|
|
50
|
+
unregisterTrigger(type: string): void;
|
|
51
|
+
/** Get all registered node types */
|
|
52
|
+
getRegisteredNodeTypes(): string[];
|
|
53
|
+
/** Get all registered trigger types */
|
|
54
|
+
getRegisteredTriggerTypes(): string[];
|
|
55
|
+
registerFlow(name: string, definition: unknown): void;
|
|
56
|
+
unregisterFlow(name: string): void;
|
|
57
|
+
listFlows(): Promise<string[]>;
|
|
58
|
+
execute(flowName: string, context?: AutomationContext): Promise<AutomationResult>;
|
|
59
|
+
private executeNode;
|
|
60
|
+
private evaluateCondition;
|
|
61
|
+
private retryExecution;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuration options for the AutomationServicePlugin.
|
|
66
|
+
*/
|
|
67
|
+
interface AutomationServicePluginOptions {
|
|
68
|
+
/** Enable debug logging for flow execution */
|
|
69
|
+
debug?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* AutomationServicePlugin — Core engine plugin
|
|
73
|
+
*
|
|
74
|
+
* Responsibilities:
|
|
75
|
+
* 1. init phase: Create engine instance, register as 'automation' service
|
|
76
|
+
* 2. start phase: Trigger 'automation:ready' hook for node plugin registration
|
|
77
|
+
* 3. destroy phase: Clean up resources
|
|
78
|
+
*
|
|
79
|
+
* Does NOT implement any specific nodes — nodes are registered by other plugins
|
|
80
|
+
* via the engine's extension API.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { LiteKernel } from '@objectstack/core';
|
|
85
|
+
* import { AutomationServicePlugin } from '@objectstack/service-automation';
|
|
86
|
+
*
|
|
87
|
+
* const kernel = new LiteKernel();
|
|
88
|
+
* kernel.use(new AutomationServicePlugin());
|
|
89
|
+
* await kernel.bootstrap();
|
|
90
|
+
*
|
|
91
|
+
* const automation = kernel.getService('automation');
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare class AutomationServicePlugin implements Plugin {
|
|
95
|
+
name: string;
|
|
96
|
+
version: string;
|
|
97
|
+
type: "standard";
|
|
98
|
+
dependencies: string[];
|
|
99
|
+
private engine?;
|
|
100
|
+
private readonly options;
|
|
101
|
+
constructor(options?: AutomationServicePluginOptions);
|
|
102
|
+
init(ctx: PluginContext): Promise<void>;
|
|
103
|
+
start(ctx: PluginContext): Promise<void>;
|
|
104
|
+
destroy(): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* CRUD Node Plugin — Provides get_record / create_record / update_record / delete_record
|
|
109
|
+
*
|
|
110
|
+
* Dependencies: service-automation (engine)
|
|
111
|
+
*
|
|
112
|
+
* In a full runtime environment these nodes would delegate to ObjectQL (data layer).
|
|
113
|
+
* This MVP implementation provides the extension point structure.
|
|
114
|
+
*/
|
|
115
|
+
declare class CrudNodesPlugin implements Plugin {
|
|
116
|
+
name: string;
|
|
117
|
+
version: string;
|
|
118
|
+
type: "standard";
|
|
119
|
+
dependencies: string[];
|
|
120
|
+
init(ctx: PluginContext): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Logic Node Plugin — Provides decision / assignment / loop nodes
|
|
125
|
+
*
|
|
126
|
+
* Dependencies: service-automation (engine)
|
|
127
|
+
*/
|
|
128
|
+
declare class LogicNodesPlugin implements Plugin {
|
|
129
|
+
name: string;
|
|
130
|
+
version: string;
|
|
131
|
+
type: "standard";
|
|
132
|
+
dependencies: string[];
|
|
133
|
+
init(ctx: PluginContext): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* HTTP + Connector Node Plugin — Provides http_request / connector_action nodes
|
|
138
|
+
*
|
|
139
|
+
* Dependencies: service-automation (engine)
|
|
140
|
+
*/
|
|
141
|
+
declare class HttpConnectorPlugin implements Plugin {
|
|
142
|
+
name: string;
|
|
143
|
+
version: string;
|
|
144
|
+
type: "standard";
|
|
145
|
+
dependencies: string[];
|
|
146
|
+
init(ctx: PluginContext): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { AutomationEngine, AutomationServicePlugin, type AutomationServicePluginOptions, CrudNodesPlugin, type FlowTrigger, HttpConnectorPlugin, LogicNodesPlugin, type NodeExecutionResult, type NodeExecutor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// src/engine.ts
|
|
2
|
+
import { FlowSchema } from "@objectstack/spec/automation";
|
|
3
|
+
var AutomationEngine = class {
|
|
4
|
+
constructor(logger) {
|
|
5
|
+
this.flows = /* @__PURE__ */ new Map();
|
|
6
|
+
this.nodeExecutors = /* @__PURE__ */ new Map();
|
|
7
|
+
this.triggers = /* @__PURE__ */ new Map();
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
}
|
|
10
|
+
// ── Plugin Extension API ──────────────────────────────
|
|
11
|
+
/** Register a node executor (called by plugins) */
|
|
12
|
+
registerNodeExecutor(executor) {
|
|
13
|
+
if (this.nodeExecutors.has(executor.type)) {
|
|
14
|
+
this.logger.warn(`Node executor '${executor.type}' replaced`);
|
|
15
|
+
}
|
|
16
|
+
this.nodeExecutors.set(executor.type, executor);
|
|
17
|
+
this.logger.info(`Node executor registered: ${executor.type}`);
|
|
18
|
+
}
|
|
19
|
+
/** Unregister a node executor (hot-unplug) */
|
|
20
|
+
unregisterNodeExecutor(type) {
|
|
21
|
+
this.nodeExecutors.delete(type);
|
|
22
|
+
this.logger.info(`Node executor unregistered: ${type}`);
|
|
23
|
+
}
|
|
24
|
+
/** Register a trigger (called by plugins) */
|
|
25
|
+
registerTrigger(trigger) {
|
|
26
|
+
this.triggers.set(trigger.type, trigger);
|
|
27
|
+
this.logger.info(`Trigger registered: ${trigger.type}`);
|
|
28
|
+
}
|
|
29
|
+
/** Unregister a trigger (hot-unplug) */
|
|
30
|
+
unregisterTrigger(type) {
|
|
31
|
+
this.triggers.delete(type);
|
|
32
|
+
this.logger.info(`Trigger unregistered: ${type}`);
|
|
33
|
+
}
|
|
34
|
+
/** Get all registered node types */
|
|
35
|
+
getRegisteredNodeTypes() {
|
|
36
|
+
return [...this.nodeExecutors.keys()];
|
|
37
|
+
}
|
|
38
|
+
/** Get all registered trigger types */
|
|
39
|
+
getRegisteredTriggerTypes() {
|
|
40
|
+
return [...this.triggers.keys()];
|
|
41
|
+
}
|
|
42
|
+
// ── IAutomationService Contract Implementation ────────
|
|
43
|
+
registerFlow(name, definition) {
|
|
44
|
+
const parsed = FlowSchema.parse(definition);
|
|
45
|
+
this.flows.set(name, parsed);
|
|
46
|
+
this.logger.info(`Flow registered: ${name}`);
|
|
47
|
+
}
|
|
48
|
+
unregisterFlow(name) {
|
|
49
|
+
this.flows.delete(name);
|
|
50
|
+
this.logger.info(`Flow unregistered: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
async listFlows() {
|
|
53
|
+
return [...this.flows.keys()];
|
|
54
|
+
}
|
|
55
|
+
async execute(flowName, context) {
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
const flow = this.flows.get(flowName);
|
|
58
|
+
if (!flow) {
|
|
59
|
+
return { success: false, error: `Flow '${flowName}' not found` };
|
|
60
|
+
}
|
|
61
|
+
const variables = /* @__PURE__ */ new Map();
|
|
62
|
+
if (flow.variables) {
|
|
63
|
+
for (const v of flow.variables) {
|
|
64
|
+
if (v.isInput && context?.params?.[v.name] !== void 0) {
|
|
65
|
+
variables.set(v.name, context.params[v.name]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (context?.record) {
|
|
70
|
+
variables.set("$record", context.record);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const startNode = flow.nodes.find((n) => n.type === "start");
|
|
74
|
+
if (!startNode) {
|
|
75
|
+
return { success: false, error: "Flow has no start node" };
|
|
76
|
+
}
|
|
77
|
+
await this.executeNode(startNode, flow, variables, context ?? {});
|
|
78
|
+
const output = {};
|
|
79
|
+
if (flow.variables) {
|
|
80
|
+
for (const v of flow.variables) {
|
|
81
|
+
if (v.isOutput) {
|
|
82
|
+
output[v.name] = variables.get(v.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
output,
|
|
89
|
+
durationMs: Date.now() - startTime
|
|
90
|
+
};
|
|
91
|
+
} catch (err) {
|
|
92
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
93
|
+
if (flow.errorHandling?.strategy === "retry") {
|
|
94
|
+
return this.retryExecution(flowName, context, startTime, flow.errorHandling);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: errorMessage,
|
|
99
|
+
durationMs: Date.now() - startTime
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ── DAG Traversal Core ──────────────────────────────────
|
|
104
|
+
async executeNode(node, flow, variables, context) {
|
|
105
|
+
if (node.type === "end") return;
|
|
106
|
+
const executor = this.nodeExecutors.get(node.type);
|
|
107
|
+
if (!executor) {
|
|
108
|
+
if (node.type !== "start") {
|
|
109
|
+
throw new Error(`No executor registered for node type '${node.type}'`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const result = await executor.execute(node, variables, context);
|
|
113
|
+
if (!result.success) {
|
|
114
|
+
throw new Error(`Node '${node.id}' failed: ${result.error}`);
|
|
115
|
+
}
|
|
116
|
+
if (result.output) {
|
|
117
|
+
for (const [key, value] of Object.entries(result.output)) {
|
|
118
|
+
variables.set(`${node.id}.${key}`, value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const outEdges = flow.edges.filter((e) => e.source === node.id);
|
|
123
|
+
for (const edge of outEdges) {
|
|
124
|
+
if (edge.condition && !this.evaluateCondition(edge.condition, variables)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const nextNode = flow.nodes.find((n) => n.id === edge.target);
|
|
128
|
+
if (nextNode) {
|
|
129
|
+
await this.executeNode(nextNode, flow, variables, context);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
evaluateCondition(expression, variables) {
|
|
134
|
+
let resolved = expression;
|
|
135
|
+
for (const [key, value] of variables) {
|
|
136
|
+
resolved = resolved.split(`{${key}}`).join(String(value));
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return new Function(`return (${resolved})`)();
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async retryExecution(flowName, context, startTime, errorHandling) {
|
|
145
|
+
const maxRetries = errorHandling.maxRetries ?? 3;
|
|
146
|
+
const delay = errorHandling.retryDelayMs ?? 1e3;
|
|
147
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
148
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
149
|
+
const result = await this.execute(flowName, context);
|
|
150
|
+
if (result.success) return result;
|
|
151
|
+
}
|
|
152
|
+
return { success: false, error: "Max retries exceeded", durationMs: Date.now() - startTime };
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/plugin.ts
|
|
157
|
+
var AutomationServicePlugin = class {
|
|
158
|
+
constructor(options = {}) {
|
|
159
|
+
this.name = "com.objectstack.service-automation";
|
|
160
|
+
this.version = "1.0.0";
|
|
161
|
+
this.type = "standard";
|
|
162
|
+
this.dependencies = [];
|
|
163
|
+
this.options = options;
|
|
164
|
+
}
|
|
165
|
+
async init(ctx) {
|
|
166
|
+
this.engine = new AutomationEngine(ctx.logger);
|
|
167
|
+
ctx.registerService("automation", this.engine);
|
|
168
|
+
if (this.options.debug) {
|
|
169
|
+
ctx.hook("automation:beforeExecute", async (flowName) => {
|
|
170
|
+
ctx.logger.debug(`[Automation] Before execute: ${flowName}`);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
ctx.logger.info("[Automation] Engine initialized");
|
|
174
|
+
}
|
|
175
|
+
async start(ctx) {
|
|
176
|
+
if (!this.engine) return;
|
|
177
|
+
await ctx.trigger("automation:ready", this.engine);
|
|
178
|
+
const nodeTypes = this.engine.getRegisteredNodeTypes();
|
|
179
|
+
ctx.logger.info(
|
|
180
|
+
`[Automation] Engine started with ${nodeTypes.length} node types: ${nodeTypes.join(", ") || "(none)"}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
async destroy() {
|
|
184
|
+
this.engine = void 0;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/plugins/crud-nodes-plugin.ts
|
|
189
|
+
var CrudNodesPlugin = class {
|
|
190
|
+
constructor() {
|
|
191
|
+
this.name = "com.objectstack.automation.crud-nodes";
|
|
192
|
+
this.version = "1.0.0";
|
|
193
|
+
this.type = "standard";
|
|
194
|
+
this.dependencies = ["com.objectstack.service-automation"];
|
|
195
|
+
}
|
|
196
|
+
async init(ctx) {
|
|
197
|
+
const engine = ctx.getService("automation");
|
|
198
|
+
engine.registerNodeExecutor({
|
|
199
|
+
type: "get_record",
|
|
200
|
+
async execute(node, _variables, _context) {
|
|
201
|
+
const config = node.config;
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
output: { records: [], object: config?.object }
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
engine.registerNodeExecutor({
|
|
209
|
+
type: "create_record",
|
|
210
|
+
async execute(node, _variables, _context) {
|
|
211
|
+
const config = node.config;
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
output: { id: "new-record-id", object: config?.object }
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
engine.registerNodeExecutor({
|
|
219
|
+
type: "update_record",
|
|
220
|
+
async execute(_node, _variables, _context) {
|
|
221
|
+
return { success: true };
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
engine.registerNodeExecutor({
|
|
225
|
+
type: "delete_record",
|
|
226
|
+
async execute(_node, _variables, _context) {
|
|
227
|
+
return { success: true };
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
ctx.logger.info("[CRUD Nodes] 4 node executors registered");
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/plugins/logic-nodes-plugin.ts
|
|
235
|
+
var LogicNodesPlugin = class {
|
|
236
|
+
constructor() {
|
|
237
|
+
this.name = "com.objectstack.automation.logic-nodes";
|
|
238
|
+
this.version = "1.0.0";
|
|
239
|
+
this.type = "standard";
|
|
240
|
+
this.dependencies = ["com.objectstack.service-automation"];
|
|
241
|
+
}
|
|
242
|
+
async init(ctx) {
|
|
243
|
+
const engine = ctx.getService("automation");
|
|
244
|
+
engine.registerNodeExecutor({
|
|
245
|
+
type: "decision",
|
|
246
|
+
async execute(node, variables, _context) {
|
|
247
|
+
const config = node.config;
|
|
248
|
+
const conditions = config?.conditions ?? [];
|
|
249
|
+
for (const cond of conditions) {
|
|
250
|
+
let expr = cond.expression;
|
|
251
|
+
for (const [k, v] of variables) {
|
|
252
|
+
expr = expr.split(`{${k}}`).join(String(v));
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
if (new Function(`return (${expr})`)()) {
|
|
256
|
+
return { success: true, branchLabel: cond.label };
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { success: true, branchLabel: "default" };
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
engine.registerNodeExecutor({
|
|
265
|
+
type: "assignment",
|
|
266
|
+
async execute(node, variables, _context) {
|
|
267
|
+
const config = node.config ?? {};
|
|
268
|
+
for (const [key, value] of Object.entries(config)) {
|
|
269
|
+
variables.set(key, value);
|
|
270
|
+
}
|
|
271
|
+
return { success: true };
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
engine.registerNodeExecutor({
|
|
275
|
+
type: "loop",
|
|
276
|
+
async execute(node, variables, _context) {
|
|
277
|
+
const config = node.config;
|
|
278
|
+
const collectionName = config?.collection;
|
|
279
|
+
if (collectionName) {
|
|
280
|
+
const collection = variables.get(collectionName);
|
|
281
|
+
if (Array.isArray(collection)) {
|
|
282
|
+
variables.set("$loopItems", collection);
|
|
283
|
+
variables.set("$loopIndex", 0);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { success: true };
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
ctx.logger.info("[Logic Nodes] 3 node executors registered");
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/plugins/http-connector-plugin.ts
|
|
294
|
+
var HttpConnectorPlugin = class {
|
|
295
|
+
constructor() {
|
|
296
|
+
this.name = "com.objectstack.automation.http-connector";
|
|
297
|
+
this.version = "1.0.0";
|
|
298
|
+
this.type = "standard";
|
|
299
|
+
this.dependencies = ["com.objectstack.service-automation"];
|
|
300
|
+
}
|
|
301
|
+
async init(ctx) {
|
|
302
|
+
const engine = ctx.getService("automation");
|
|
303
|
+
engine.registerNodeExecutor({
|
|
304
|
+
type: "http_request",
|
|
305
|
+
async execute(node, _variables, _context) {
|
|
306
|
+
const config = node.config;
|
|
307
|
+
const url = config?.url;
|
|
308
|
+
const method = config?.method ?? "GET";
|
|
309
|
+
const headers = config?.headers;
|
|
310
|
+
const body = config?.body;
|
|
311
|
+
if (!url) {
|
|
312
|
+
return { success: false, error: "http_request: url is required" };
|
|
313
|
+
}
|
|
314
|
+
const response = await fetch(url, {
|
|
315
|
+
method,
|
|
316
|
+
headers,
|
|
317
|
+
body: body ? JSON.stringify(body) : void 0
|
|
318
|
+
});
|
|
319
|
+
const data = await response.json();
|
|
320
|
+
return {
|
|
321
|
+
success: response.ok,
|
|
322
|
+
output: { response: data, status: response.status },
|
|
323
|
+
error: response.ok ? void 0 : `HTTP ${response.status}`
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
engine.registerNodeExecutor({
|
|
328
|
+
type: "connector_action",
|
|
329
|
+
async execute(node, _variables, _context) {
|
|
330
|
+
const connectorConfig = node.connectorConfig;
|
|
331
|
+
if (!connectorConfig) {
|
|
332
|
+
return { success: false, error: "connector_action: connectorConfig is required" };
|
|
333
|
+
}
|
|
334
|
+
ctx.logger.info(
|
|
335
|
+
`Connector action: ${connectorConfig.connectorId}.${connectorConfig.actionId}`
|
|
336
|
+
);
|
|
337
|
+
return { success: true, output: { connectorResult: {} } };
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
ctx.logger.info("[HTTP Connector] 2 node executors registered");
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
export {
|
|
344
|
+
AutomationEngine,
|
|
345
|
+
AutomationServicePlugin,
|
|
346
|
+
CrudNodesPlugin,
|
|
347
|
+
HttpConnectorPlugin,
|
|
348
|
+
LogicNodesPlugin
|
|
349
|
+
};
|
|
350
|
+
//# sourceMappingURL=index.js.map
|