@objectstack/service-automation 9.5.0 → 9.6.0
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/dist/index.cjs +86 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -16
- package/dist/index.d.ts +46 -16
- package/dist/index.js +86 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.d.cts
CHANGED
|
@@ -137,6 +137,36 @@ interface RegisteredConnector {
|
|
|
137
137
|
readonly def: Connector;
|
|
138
138
|
readonly handlers: Record<string, ConnectorActionHandler>;
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Context handed to a named handler function invoked from a `script` node
|
|
142
|
+
* (#1870). Mirrors {@link ConnectorActionContext} but carries the node's mapped
|
|
143
|
+
* `input` so the function reads its arguments without reaching into the raw
|
|
144
|
+
* variable map. The function's return value becomes the node output.
|
|
145
|
+
*/
|
|
146
|
+
interface FlowFunctionContext {
|
|
147
|
+
/** Inputs mapped from the node's `config.inputs` (already in scope). */
|
|
148
|
+
readonly input: Record<string, unknown>;
|
|
149
|
+
/** Live flow variable map — read prior-node output / write results. */
|
|
150
|
+
readonly variables: Map<string, unknown>;
|
|
151
|
+
/** The flow execution / trigger context. */
|
|
152
|
+
readonly automation: AutomationContext;
|
|
153
|
+
readonly logger: Logger;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A named handler function callable from a `script` node. Returns the node's
|
|
157
|
+
* output (any JSON-serializable value); returning `undefined` yields an empty
|
|
158
|
+
* output. Authored packages contribute these via `defineStack({ functions })`,
|
|
159
|
+
* which the host bridges in through {@link AutomationEngine.setFunctionResolver}.
|
|
160
|
+
*/
|
|
161
|
+
type FlowFunctionHandler = (ctx: FlowFunctionContext) => unknown | Promise<unknown>;
|
|
162
|
+
/**
|
|
163
|
+
* Resolves a function name to its handler. Injected by the host (the automation
|
|
164
|
+
* plugin bridges it to ObjectQL's `resolveFunction`, fed by `bundle.functions`),
|
|
165
|
+
* so the engine stays decoupled from any specific function registry. Returns
|
|
166
|
+
* `undefined` for an unknown name, letting the `script` node fail the step
|
|
167
|
+
* loudly instead of silently no-op'ing (#1870).
|
|
168
|
+
*/
|
|
169
|
+
type FlowFunctionResolver = (name: string) => FlowFunctionHandler | undefined;
|
|
140
170
|
/**
|
|
141
171
|
* A designer-facing view of one connector action — identity + its JSON-Schema
|
|
142
172
|
* input/output. The runtime handler is intentionally omitted; this is metadata.
|
|
@@ -301,6 +331,8 @@ declare class AutomationEngine implements IAutomationService {
|
|
|
301
331
|
private boundFlowTriggers;
|
|
302
332
|
/** Connectors registered by integration plugins, keyed by connector name (ADR-0018 §Addendum). */
|
|
303
333
|
private connectors;
|
|
334
|
+
/** Bridge to the host function registry for `script`-node calls (#1870), if wired. */
|
|
335
|
+
private functionResolver;
|
|
304
336
|
private executionLogs;
|
|
305
337
|
private readonly maxLogSize;
|
|
306
338
|
private logger;
|
|
@@ -415,6 +447,20 @@ declare class AutomationEngine implements IAutomationService {
|
|
|
415
447
|
* is not registered, so the node can fail the step with a clear error.
|
|
416
448
|
*/
|
|
417
449
|
resolveConnectorAction(connectorId: string, actionId: string): ConnectorActionHandler | undefined;
|
|
450
|
+
/**
|
|
451
|
+
* Wire the engine to the host's named-function registry (#1870). The
|
|
452
|
+
* automation plugin calls this in `start()` with a resolver backed by
|
|
453
|
+
* ObjectQL's `resolveFunction` (populated from `bundle.functions` /
|
|
454
|
+
* `defineStack({ functions })`), so a `script` node can invoke an
|
|
455
|
+
* authored function by name. Passing `null` detaches the bridge.
|
|
456
|
+
*/
|
|
457
|
+
setFunctionResolver(resolver: FlowFunctionResolver | null): void;
|
|
458
|
+
/**
|
|
459
|
+
* Resolve a named function for a `script` node. Returns `undefined` when no
|
|
460
|
+
* resolver is wired or the name is unregistered — the node then fails the
|
|
461
|
+
* step with a clear error rather than silently no-op'ing.
|
|
462
|
+
*/
|
|
463
|
+
resolveFunction(name: string): FlowFunctionHandler | undefined;
|
|
418
464
|
/** Get all registered connector names. */
|
|
419
465
|
getRegisteredConnectors(): string[];
|
|
420
466
|
/**
|
|
@@ -5009,22 +5055,6 @@ declare function registerLogicNodes(engine: AutomationEngine, ctx: PluginContext
|
|
|
5009
5055
|
*/
|
|
5010
5056
|
declare function registerCrudNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
|
5011
5057
|
|
|
5012
|
-
/**
|
|
5013
|
-
* Screen / Script built-in nodes — 'screen' and 'script' executors.
|
|
5014
|
-
* Part of the core flow capability, so the {@link AutomationServicePlugin}
|
|
5015
|
-
* seeds them directly (ADR-0018) rather than shipping a separate plugin.
|
|
5016
|
-
*
|
|
5017
|
-
* - 'screen' nodes collect user input. A screen that declares `config.fields`
|
|
5018
|
-
* (or sets `config.waitForInput === true`) suspends the run on entry via the
|
|
5019
|
-
* engine's durable pause (ADR-0019), surfacing a `ScreenSpec` for the client
|
|
5020
|
-
* to render; the run continues via `resume()` with the collected values (set
|
|
5021
|
-
* as bare flow variables). A field-less screen — or one with
|
|
5022
|
-
* `waitForInput === false` — stays a server pass-through (input vars, if any,
|
|
5023
|
-
* are already injected from `context.params`).
|
|
5024
|
-
* - 'script' nodes dispatch by `config.actionType`. Currently only 'email'
|
|
5025
|
-
* has a (logger-backed) implementation; unknown action types still succeed
|
|
5026
|
-
* so flows can continue and downstream nodes can react.
|
|
5027
|
-
*/
|
|
5028
5058
|
declare function registerScreenNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
|
5029
5059
|
|
|
5030
5060
|
declare function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -137,6 +137,36 @@ interface RegisteredConnector {
|
|
|
137
137
|
readonly def: Connector;
|
|
138
138
|
readonly handlers: Record<string, ConnectorActionHandler>;
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Context handed to a named handler function invoked from a `script` node
|
|
142
|
+
* (#1870). Mirrors {@link ConnectorActionContext} but carries the node's mapped
|
|
143
|
+
* `input` so the function reads its arguments without reaching into the raw
|
|
144
|
+
* variable map. The function's return value becomes the node output.
|
|
145
|
+
*/
|
|
146
|
+
interface FlowFunctionContext {
|
|
147
|
+
/** Inputs mapped from the node's `config.inputs` (already in scope). */
|
|
148
|
+
readonly input: Record<string, unknown>;
|
|
149
|
+
/** Live flow variable map — read prior-node output / write results. */
|
|
150
|
+
readonly variables: Map<string, unknown>;
|
|
151
|
+
/** The flow execution / trigger context. */
|
|
152
|
+
readonly automation: AutomationContext;
|
|
153
|
+
readonly logger: Logger;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A named handler function callable from a `script` node. Returns the node's
|
|
157
|
+
* output (any JSON-serializable value); returning `undefined` yields an empty
|
|
158
|
+
* output. Authored packages contribute these via `defineStack({ functions })`,
|
|
159
|
+
* which the host bridges in through {@link AutomationEngine.setFunctionResolver}.
|
|
160
|
+
*/
|
|
161
|
+
type FlowFunctionHandler = (ctx: FlowFunctionContext) => unknown | Promise<unknown>;
|
|
162
|
+
/**
|
|
163
|
+
* Resolves a function name to its handler. Injected by the host (the automation
|
|
164
|
+
* plugin bridges it to ObjectQL's `resolveFunction`, fed by `bundle.functions`),
|
|
165
|
+
* so the engine stays decoupled from any specific function registry. Returns
|
|
166
|
+
* `undefined` for an unknown name, letting the `script` node fail the step
|
|
167
|
+
* loudly instead of silently no-op'ing (#1870).
|
|
168
|
+
*/
|
|
169
|
+
type FlowFunctionResolver = (name: string) => FlowFunctionHandler | undefined;
|
|
140
170
|
/**
|
|
141
171
|
* A designer-facing view of one connector action — identity + its JSON-Schema
|
|
142
172
|
* input/output. The runtime handler is intentionally omitted; this is metadata.
|
|
@@ -301,6 +331,8 @@ declare class AutomationEngine implements IAutomationService {
|
|
|
301
331
|
private boundFlowTriggers;
|
|
302
332
|
/** Connectors registered by integration plugins, keyed by connector name (ADR-0018 §Addendum). */
|
|
303
333
|
private connectors;
|
|
334
|
+
/** Bridge to the host function registry for `script`-node calls (#1870), if wired. */
|
|
335
|
+
private functionResolver;
|
|
304
336
|
private executionLogs;
|
|
305
337
|
private readonly maxLogSize;
|
|
306
338
|
private logger;
|
|
@@ -415,6 +447,20 @@ declare class AutomationEngine implements IAutomationService {
|
|
|
415
447
|
* is not registered, so the node can fail the step with a clear error.
|
|
416
448
|
*/
|
|
417
449
|
resolveConnectorAction(connectorId: string, actionId: string): ConnectorActionHandler | undefined;
|
|
450
|
+
/**
|
|
451
|
+
* Wire the engine to the host's named-function registry (#1870). The
|
|
452
|
+
* automation plugin calls this in `start()` with a resolver backed by
|
|
453
|
+
* ObjectQL's `resolveFunction` (populated from `bundle.functions` /
|
|
454
|
+
* `defineStack({ functions })`), so a `script` node can invoke an
|
|
455
|
+
* authored function by name. Passing `null` detaches the bridge.
|
|
456
|
+
*/
|
|
457
|
+
setFunctionResolver(resolver: FlowFunctionResolver | null): void;
|
|
458
|
+
/**
|
|
459
|
+
* Resolve a named function for a `script` node. Returns `undefined` when no
|
|
460
|
+
* resolver is wired or the name is unregistered — the node then fails the
|
|
461
|
+
* step with a clear error rather than silently no-op'ing.
|
|
462
|
+
*/
|
|
463
|
+
resolveFunction(name: string): FlowFunctionHandler | undefined;
|
|
418
464
|
/** Get all registered connector names. */
|
|
419
465
|
getRegisteredConnectors(): string[];
|
|
420
466
|
/**
|
|
@@ -5009,22 +5055,6 @@ declare function registerLogicNodes(engine: AutomationEngine, ctx: PluginContext
|
|
|
5009
5055
|
*/
|
|
5010
5056
|
declare function registerCrudNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
|
5011
5057
|
|
|
5012
|
-
/**
|
|
5013
|
-
* Screen / Script built-in nodes — 'screen' and 'script' executors.
|
|
5014
|
-
* Part of the core flow capability, so the {@link AutomationServicePlugin}
|
|
5015
|
-
* seeds them directly (ADR-0018) rather than shipping a separate plugin.
|
|
5016
|
-
*
|
|
5017
|
-
* - 'screen' nodes collect user input. A screen that declares `config.fields`
|
|
5018
|
-
* (or sets `config.waitForInput === true`) suspends the run on entry via the
|
|
5019
|
-
* engine's durable pause (ADR-0019), surfacing a `ScreenSpec` for the client
|
|
5020
|
-
* to render; the run continues via `resume()` with the collected values (set
|
|
5021
|
-
* as bare flow variables). A field-less screen — or one with
|
|
5022
|
-
* `waitForInput === false` — stays a server pass-through (input vars, if any,
|
|
5023
|
-
* are already injected from `context.params`).
|
|
5024
|
-
* - 'script' nodes dispatch by `config.actionType`. Currently only 'email'
|
|
5025
|
-
* has a (logger-backed) implementation; unknown action types still succeed
|
|
5026
|
-
* so flows can continue and downstream nodes can react.
|
|
5027
|
-
*/
|
|
5028
5058
|
declare function registerScreenNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
|
5029
5059
|
|
|
5030
5060
|
declare function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext): void;
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ var _AutomationEngine = class _AutomationEngine {
|
|
|
30
30
|
this.boundFlowTriggers = /* @__PURE__ */ new Map();
|
|
31
31
|
/** Connectors registered by integration plugins, keyed by connector name (ADR-0018 §Addendum). */
|
|
32
32
|
this.connectors = /* @__PURE__ */ new Map();
|
|
33
|
+
/** Bridge to the host function registry for `script`-node calls (#1870), if wired. */
|
|
34
|
+
this.functionResolver = null;
|
|
33
35
|
this.executionLogs = [];
|
|
34
36
|
/**
|
|
35
37
|
* Runs paused at a node, keyed by runId (ADR-0019). In-memory hot cache —
|
|
@@ -317,6 +319,24 @@ var _AutomationEngine = class _AutomationEngine {
|
|
|
317
319
|
resolveConnectorAction(connectorId, actionId) {
|
|
318
320
|
return this.connectors.get(connectorId)?.handlers[actionId];
|
|
319
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Wire the engine to the host's named-function registry (#1870). The
|
|
324
|
+
* automation plugin calls this in `start()` with a resolver backed by
|
|
325
|
+
* ObjectQL's `resolveFunction` (populated from `bundle.functions` /
|
|
326
|
+
* `defineStack({ functions })`), so a `script` node can invoke an
|
|
327
|
+
* authored function by name. Passing `null` detaches the bridge.
|
|
328
|
+
*/
|
|
329
|
+
setFunctionResolver(resolver) {
|
|
330
|
+
this.functionResolver = resolver;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Resolve a named function for a `script` node. Returns `undefined` when no
|
|
334
|
+
* resolver is wired or the name is unregistered — the node then fails the
|
|
335
|
+
* step with a clear error rather than silently no-op'ing.
|
|
336
|
+
*/
|
|
337
|
+
resolveFunction(name) {
|
|
338
|
+
return this.functionResolver?.(name) ?? void 0;
|
|
339
|
+
}
|
|
320
340
|
/** Get all registered connector names. */
|
|
321
341
|
getRegisteredConnectors() {
|
|
322
342
|
return [...this.connectors.keys()];
|
|
@@ -1860,7 +1880,7 @@ function resolveToken(token, variables, context) {
|
|
|
1860
1880
|
if (path[0] === "Email") return resolvePath(context.user, ["email", ...path.slice(1)]) ?? void 0;
|
|
1861
1881
|
return resolvePath(context.user, path);
|
|
1862
1882
|
}
|
|
1863
|
-
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]
|
|
1883
|
+
if (/^[A-Za-z_$][\w$]*(?:\.(?:[A-Za-z_$][\w$]*|\d+))*$/.test(trimmed)) {
|
|
1864
1884
|
const segments = trimmed.split(".");
|
|
1865
1885
|
const head = segments[0];
|
|
1866
1886
|
if (variables.has(head)) {
|
|
@@ -2234,14 +2254,21 @@ function registerCrudNodes(engine, ctx) {
|
|
|
2234
2254
|
const data = getData();
|
|
2235
2255
|
if (!data) {
|
|
2236
2256
|
ctx.logger.warn(`[create_record] no data engine; skipping ${objectName}`);
|
|
2237
|
-
|
|
2238
|
-
|
|
2257
|
+
const mockId = `mock-${objectName}-${Date.now()}`;
|
|
2258
|
+
if (outputVariable) variables.set(outputVariable, { id: mockId });
|
|
2259
|
+
return { success: true, output: { id: mockId, object: objectName } };
|
|
2239
2260
|
}
|
|
2240
2261
|
try {
|
|
2241
2262
|
const created = await data.insert(objectName, fields);
|
|
2242
|
-
const
|
|
2243
|
-
|
|
2244
|
-
|
|
2263
|
+
const createdRecord = Array.isArray(created) ? created[0] : created;
|
|
2264
|
+
const insertedId = createdRecord && typeof createdRecord === "object" ? createdRecord.id : createdRecord;
|
|
2265
|
+
if (outputVariable) {
|
|
2266
|
+
variables.set(
|
|
2267
|
+
outputVariable,
|
|
2268
|
+
createdRecord && typeof createdRecord === "object" ? createdRecord : { id: insertedId }
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
return { success: true, output: { id: insertedId, record: createdRecord, object: objectName } };
|
|
2245
2272
|
} catch (err) {
|
|
2246
2273
|
return { success: false, error: `create_record(${objectName}) failed: ${err.message}` };
|
|
2247
2274
|
}
|
|
@@ -2308,6 +2335,7 @@ function registerCrudNodes(engine, ctx) {
|
|
|
2308
2335
|
|
|
2309
2336
|
// src/builtin/screen-nodes.ts
|
|
2310
2337
|
import { defineActionDescriptor as defineActionDescriptor7 } from "@objectstack/spec/automation";
|
|
2338
|
+
var SCRIPT_BUILTIN_ACTION_TYPES = /* @__PURE__ */ new Set(["email", "slack"]);
|
|
2311
2339
|
function registerScreenNodes(engine, ctx) {
|
|
2312
2340
|
engine.registerNodeExecutor({
|
|
2313
2341
|
type: "screen",
|
|
@@ -2363,24 +2391,53 @@ function registerScreenNodes(engine, ctx) {
|
|
|
2363
2391
|
category: "logic",
|
|
2364
2392
|
source: "builtin"
|
|
2365
2393
|
}),
|
|
2366
|
-
async execute(node,
|
|
2394
|
+
async execute(node, variables, context) {
|
|
2367
2395
|
const cfg = node.config ?? {};
|
|
2368
|
-
const
|
|
2369
|
-
|
|
2396
|
+
const fnRaw = cfg.function ?? cfg.functionName;
|
|
2397
|
+
const fnName = typeof fnRaw === "string" && fnRaw.trim() ? fnRaw.trim() : void 0;
|
|
2398
|
+
const actionType = typeof cfg.actionType === "string" && cfg.actionType.trim() ? cfg.actionType.trim() : void 0;
|
|
2399
|
+
if (!fnName && actionType && SCRIPT_BUILTIN_ACTION_TYPES.has(actionType)) {
|
|
2370
2400
|
ctx.logger.info(
|
|
2371
|
-
`[Script
|
|
2401
|
+
`[Script:${actionType}] template=${String(cfg.template)} recipients=${JSON.stringify(cfg.recipients)} vars=${JSON.stringify(cfg.variables)}`
|
|
2372
2402
|
);
|
|
2373
2403
|
return {
|
|
2374
2404
|
success: true,
|
|
2375
|
-
output: {
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2405
|
+
output: { actionType, template: cfg.template, recipients: cfg.recipients }
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
const inlineScript = typeof cfg.script === "string" && cfg.script.trim() ? cfg.script : void 0;
|
|
2409
|
+
if (!fnName && inlineScript) {
|
|
2410
|
+
ctx.logger.warn(
|
|
2411
|
+
`[Script] node '${node.id}': inline \`config.script\` is not executed by the built-in runtime (no server-side JS sandbox) \u2014 this node is a no-op. To run server logic, move it into a registered function and call it via \`config.function\` + \`defineStack({ functions })\`.`
|
|
2412
|
+
);
|
|
2413
|
+
return { success: true, output: { script: "not-executed" } };
|
|
2414
|
+
}
|
|
2415
|
+
const target = fnName ?? (actionType === "invoke_function" ? void 0 : actionType);
|
|
2416
|
+
if (!target) {
|
|
2417
|
+
return {
|
|
2418
|
+
success: false,
|
|
2419
|
+
error: actionType === "invoke_function" ? `script node '${node.id}': actionType 'invoke_function' requires \`config.function\` (or \`functionName\`) naming the function to call.` : `script node '${node.id}': declares neither \`actionType\` nor \`function\` \u2014 nothing to run.`
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
const handler = engine.resolveFunction(target);
|
|
2423
|
+
if (!handler) {
|
|
2424
|
+
return {
|
|
2425
|
+
success: false,
|
|
2426
|
+
error: `script node '${node.id}': '${target}' is not a built-in action (${[...SCRIPT_BUILTIN_ACTION_TYPES].join(", ")}) and no function named '${target}' is registered. Register it via \`defineStack({ functions: { '${target}': fn } })\`, or fix the name (#1870).`
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
const input = interpolate(cfg.inputs ?? cfg.input ?? {}, variables, context);
|
|
2430
|
+
const outputVariable = typeof cfg.outputVariable === "string" && cfg.outputVariable.trim() ? cfg.outputVariable.trim() : void 0;
|
|
2431
|
+
try {
|
|
2432
|
+
const result = await handler({ input, variables, automation: context, logger: ctx.logger });
|
|
2433
|
+
if (outputVariable) variables.set(outputVariable, result);
|
|
2434
|
+
return { success: true, output: { function: target, result } };
|
|
2435
|
+
} catch (err) {
|
|
2436
|
+
return {
|
|
2437
|
+
success: false,
|
|
2438
|
+
error: `script function '${target}' (node '${node.id}') failed: ${err.message}`
|
|
2380
2439
|
};
|
|
2381
2440
|
}
|
|
2382
|
-
ctx.logger.info(`[Script:${actionType}] node=${node.id} executed (no-op handler)`);
|
|
2383
|
-
return { success: true, output: { actionType } };
|
|
2384
2441
|
}
|
|
2385
2442
|
});
|
|
2386
2443
|
ctx.logger.info("[Screen/Script Nodes] 2 built-in node executors registered");
|
|
@@ -3030,6 +3087,18 @@ var AutomationServicePlugin = class {
|
|
|
3030
3087
|
ctx.logger.info("[Automation] No ObjectQL engine \u2014 suspended runs kept in-memory only");
|
|
3031
3088
|
}
|
|
3032
3089
|
}
|
|
3090
|
+
try {
|
|
3091
|
+
const fnRegistry = ctx.getService("objectql");
|
|
3092
|
+
if (fnRegistry && typeof fnRegistry.resolveFunction === "function") {
|
|
3093
|
+
this.engine.setFunctionResolver((name) => {
|
|
3094
|
+
const fn = fnRegistry.resolveFunction(name);
|
|
3095
|
+
return typeof fn === "function" ? (fnCtx) => fn(fnCtx) : void 0;
|
|
3096
|
+
});
|
|
3097
|
+
ctx.logger.debug("[Automation] script-node function registry bridged to objectql.resolveFunction");
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
ctx.logger.debug("[Automation] objectql not present \u2014 script-node function calls will fail loudly when used");
|
|
3101
|
+
}
|
|
3033
3102
|
try {
|
|
3034
3103
|
const ql = ctx.getService("objectql");
|
|
3035
3104
|
if (!ql) {
|