@objectstack/service-automation 9.5.1 → 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 CHANGED
@@ -67,6 +67,8 @@ var _AutomationEngine = class _AutomationEngine {
67
67
  this.boundFlowTriggers = /* @__PURE__ */ new Map();
68
68
  /** Connectors registered by integration plugins, keyed by connector name (ADR-0018 §Addendum). */
69
69
  this.connectors = /* @__PURE__ */ new Map();
70
+ /** Bridge to the host function registry for `script`-node calls (#1870), if wired. */
71
+ this.functionResolver = null;
70
72
  this.executionLogs = [];
71
73
  /**
72
74
  * Runs paused at a node, keyed by runId (ADR-0019). In-memory hot cache —
@@ -354,6 +356,24 @@ var _AutomationEngine = class _AutomationEngine {
354
356
  resolveConnectorAction(connectorId, actionId) {
355
357
  return this.connectors.get(connectorId)?.handlers[actionId];
356
358
  }
359
+ /**
360
+ * Wire the engine to the host's named-function registry (#1870). The
361
+ * automation plugin calls this in `start()` with a resolver backed by
362
+ * ObjectQL's `resolveFunction` (populated from `bundle.functions` /
363
+ * `defineStack({ functions })`), so a `script` node can invoke an
364
+ * authored function by name. Passing `null` detaches the bridge.
365
+ */
366
+ setFunctionResolver(resolver) {
367
+ this.functionResolver = resolver;
368
+ }
369
+ /**
370
+ * Resolve a named function for a `script` node. Returns `undefined` when no
371
+ * resolver is wired or the name is unregistered — the node then fails the
372
+ * step with a clear error rather than silently no-op'ing.
373
+ */
374
+ resolveFunction(name) {
375
+ return this.functionResolver?.(name) ?? void 0;
376
+ }
357
377
  /** Get all registered connector names. */
358
378
  getRegisteredConnectors() {
359
379
  return [...this.connectors.keys()];
@@ -1897,7 +1917,7 @@ function resolveToken(token, variables, context) {
1897
1917
  if (path[0] === "Email") return resolvePath(context.user, ["email", ...path.slice(1)]) ?? void 0;
1898
1918
  return resolvePath(context.user, path);
1899
1919
  }
1900
- if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(trimmed)) {
1920
+ if (/^[A-Za-z_$][\w$]*(?:\.(?:[A-Za-z_$][\w$]*|\d+))*$/.test(trimmed)) {
1901
1921
  const segments = trimmed.split(".");
1902
1922
  const head = segments[0];
1903
1923
  if (variables.has(head)) {
@@ -2271,14 +2291,21 @@ function registerCrudNodes(engine, ctx) {
2271
2291
  const data = getData();
2272
2292
  if (!data) {
2273
2293
  ctx.logger.warn(`[create_record] no data engine; skipping ${objectName}`);
2274
- if (outputVariable) variables.set(outputVariable, `mock-${objectName}-${Date.now()}`);
2275
- return { success: true, output: { id: `mock-${objectName}-${Date.now()}`, object: objectName } };
2294
+ const mockId = `mock-${objectName}-${Date.now()}`;
2295
+ if (outputVariable) variables.set(outputVariable, { id: mockId });
2296
+ return { success: true, output: { id: mockId, object: objectName } };
2276
2297
  }
2277
2298
  try {
2278
2299
  const created = await data.insert(objectName, fields);
2279
- const insertedId = Array.isArray(created) ? created[0]?.id : created?.id ?? created;
2280
- if (outputVariable) variables.set(outputVariable, insertedId);
2281
- return { success: true, output: { id: insertedId, record: created, object: objectName } };
2300
+ const createdRecord = Array.isArray(created) ? created[0] : created;
2301
+ const insertedId = createdRecord && typeof createdRecord === "object" ? createdRecord.id : createdRecord;
2302
+ if (outputVariable) {
2303
+ variables.set(
2304
+ outputVariable,
2305
+ createdRecord && typeof createdRecord === "object" ? createdRecord : { id: insertedId }
2306
+ );
2307
+ }
2308
+ return { success: true, output: { id: insertedId, record: createdRecord, object: objectName } };
2282
2309
  } catch (err) {
2283
2310
  return { success: false, error: `create_record(${objectName}) failed: ${err.message}` };
2284
2311
  }
@@ -2345,6 +2372,7 @@ function registerCrudNodes(engine, ctx) {
2345
2372
 
2346
2373
  // src/builtin/screen-nodes.ts
2347
2374
  var import_automation7 = require("@objectstack/spec/automation");
2375
+ var SCRIPT_BUILTIN_ACTION_TYPES = /* @__PURE__ */ new Set(["email", "slack"]);
2348
2376
  function registerScreenNodes(engine, ctx) {
2349
2377
  engine.registerNodeExecutor({
2350
2378
  type: "screen",
@@ -2400,24 +2428,53 @@ function registerScreenNodes(engine, ctx) {
2400
2428
  category: "logic",
2401
2429
  source: "builtin"
2402
2430
  }),
2403
- async execute(node, _variables, _context) {
2431
+ async execute(node, variables, context) {
2404
2432
  const cfg = node.config ?? {};
2405
- const actionType = cfg.actionType ?? "noop";
2406
- if (actionType === "email") {
2433
+ const fnRaw = cfg.function ?? cfg.functionName;
2434
+ const fnName = typeof fnRaw === "string" && fnRaw.trim() ? fnRaw.trim() : void 0;
2435
+ const actionType = typeof cfg.actionType === "string" && cfg.actionType.trim() ? cfg.actionType.trim() : void 0;
2436
+ if (!fnName && actionType && SCRIPT_BUILTIN_ACTION_TYPES.has(actionType)) {
2407
2437
  ctx.logger.info(
2408
- `[Script:email] template=${String(cfg.template)} recipients=${JSON.stringify(cfg.recipients)} vars=${JSON.stringify(cfg.variables)}`
2438
+ `[Script:${actionType}] template=${String(cfg.template)} recipients=${JSON.stringify(cfg.recipients)} vars=${JSON.stringify(cfg.variables)}`
2409
2439
  );
2410
2440
  return {
2411
2441
  success: true,
2412
- output: {
2413
- actionType,
2414
- template: cfg.template,
2415
- recipients: cfg.recipients
2416
- }
2442
+ output: { actionType, template: cfg.template, recipients: cfg.recipients }
2443
+ };
2444
+ }
2445
+ const inlineScript = typeof cfg.script === "string" && cfg.script.trim() ? cfg.script : void 0;
2446
+ if (!fnName && inlineScript) {
2447
+ ctx.logger.warn(
2448
+ `[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 })\`.`
2449
+ );
2450
+ return { success: true, output: { script: "not-executed" } };
2451
+ }
2452
+ const target = fnName ?? (actionType === "invoke_function" ? void 0 : actionType);
2453
+ if (!target) {
2454
+ return {
2455
+ success: false,
2456
+ 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.`
2457
+ };
2458
+ }
2459
+ const handler = engine.resolveFunction(target);
2460
+ if (!handler) {
2461
+ return {
2462
+ success: false,
2463
+ 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).`
2464
+ };
2465
+ }
2466
+ const input = interpolate(cfg.inputs ?? cfg.input ?? {}, variables, context);
2467
+ const outputVariable = typeof cfg.outputVariable === "string" && cfg.outputVariable.trim() ? cfg.outputVariable.trim() : void 0;
2468
+ try {
2469
+ const result = await handler({ input, variables, automation: context, logger: ctx.logger });
2470
+ if (outputVariable) variables.set(outputVariable, result);
2471
+ return { success: true, output: { function: target, result } };
2472
+ } catch (err) {
2473
+ return {
2474
+ success: false,
2475
+ error: `script function '${target}' (node '${node.id}') failed: ${err.message}`
2417
2476
  };
2418
2477
  }
2419
- ctx.logger.info(`[Script:${actionType}] node=${node.id} executed (no-op handler)`);
2420
- return { success: true, output: { actionType } };
2421
2478
  }
2422
2479
  });
2423
2480
  ctx.logger.info("[Screen/Script Nodes] 2 built-in node executors registered");
@@ -3067,6 +3124,18 @@ var AutomationServicePlugin = class {
3067
3124
  ctx.logger.info("[Automation] No ObjectQL engine \u2014 suspended runs kept in-memory only");
3068
3125
  }
3069
3126
  }
3127
+ try {
3128
+ const fnRegistry = ctx.getService("objectql");
3129
+ if (fnRegistry && typeof fnRegistry.resolveFunction === "function") {
3130
+ this.engine.setFunctionResolver((name) => {
3131
+ const fn = fnRegistry.resolveFunction(name);
3132
+ return typeof fn === "function" ? (fnCtx) => fn(fnCtx) : void 0;
3133
+ });
3134
+ ctx.logger.debug("[Automation] script-node function registry bridged to objectql.resolveFunction");
3135
+ }
3136
+ } catch {
3137
+ ctx.logger.debug("[Automation] objectql not present \u2014 script-node function calls will fail loudly when used");
3138
+ }
3070
3139
  try {
3071
3140
  const ql = ctx.getService("objectql");
3072
3141
  if (!ql) {