-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathconnector-nodes.ts
More file actions
99 lines (93 loc) · 4.58 KB
/
Copy pathconnector-nodes.ts
File metadata and controls
99 lines (93 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import type { PluginContext } from '@objectstack/core';
import { defineActionDescriptor } from '@objectstack/spec/automation';
import type { AutomationEngine, ConnectorActionContext } from '../engine.js';
/**
* Connector built-in node — `connector_action` (generic integration dispatch).
*
* Part of the platform baseline alongside `http` (ADR-0018 §Addendum):
* where `http` calls *any raw URL*, `connector_action` invokes *any
* registered connector's action*. The platform ships the generic dispatch node
* + an (initially empty) connector registry on the engine; concrete connectors
* — `connector-rest`, `connector-slack`, `connector-salesforce`, … — populate
* the registry at runtime via `engine.registerConnector()`.
*
* Because the registry starts empty, a flow referencing a connector that no
* plugin has registered fails the *step* with a clear error rather than failing
* to register — graceful degradation matching `http`'s fail-soft style.
*/
export function registerConnectorNodes(engine: AutomationEngine, ctx: PluginContext): void {
engine.registerNodeExecutor({
type: 'connector_action',
descriptor: defineActionDescriptor({
type: 'connector_action',
version: '1.0.0',
name: 'Connector Action',
description:
'Invoke an action on a registered connector (Slack, Salesforce, a REST API, …). '
+ 'The connector itself is contributed by an integration plugin via registerConnector().',
icon: 'plug',
category: 'io',
source: 'builtin',
supportsRetry: true,
// Present in both authoring paradigms (ADR-0018 §registry table;
// workflow_rule retired per ADR-0019).
paradigms: ['flow', 'approval'],
// Config contract — drives the Studio property form and flow validation.
configSchema: {
type: 'object',
required: ['connectorId', 'actionId'],
properties: {
connectorId: { type: 'string', description: 'Registered connector name' },
actionId: { type: 'string', description: 'Action key declared by the connector' },
input: { type: 'object', description: 'Mapped inputs for the action' },
},
},
}),
async execute(node, variables, context) {
const cfg = node.connectorConfig;
if (!cfg?.connectorId || !cfg?.actionId) {
return {
success: false,
error: `connector_action '${node.id}': connectorConfig.connectorId and .actionId are required`,
};
}
const handler = engine.resolveConnectorAction(cfg.connectorId, cfg.actionId);
if (!handler) {
// A degraded declarative instance (#3017) is registered but has no
// handlers — say WHY dispatch fails and that recovery is automatic,
// instead of the generic wiring hint below.
const degraded = engine.getConnectorDegradedReason(cfg.connectorId);
if (degraded) {
return {
success: false,
error:
`connector_action '${node.id}': connector '${cfg.connectorId}' is degraded — ${degraded}. `
+ `Dispatch is unavailable until its upstream recovers; the platform retries automatically (#3017).`,
};
}
return {
success: false,
error:
`connector_action '${node.id}': no handler for `
+ `'${cfg.connectorId}.${cfg.actionId}' — is the connector plugin registered?`,
};
}
const handlerCtx: ConnectorActionContext = {
variables,
automation: context,
logger: ctx.logger,
};
try {
const output = await handler((cfg.input ?? {}) as Record<string, unknown>, handlerCtx);
return { success: true, output };
} catch (err) {
return {
success: false,
error: `connector_action(${cfg.connectorId}.${cfg.actionId}) failed: ${(err as Error).message}`,
};
}
},
});
ctx.logger.info('[Connector] 1 built-in node executor registered (connector_action)');
}