@uipath/maestro-builder-sdk 6.68.4 → 6.68.6
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/api-index.md +34 -33
- package/dist/api-members.md +162 -158
- package/dist/case/case-sdk.d.ts +61 -11
- package/dist/case/case-sdk.js +25 -5
- package/dist/case/check.js +12 -8
- package/dist/case/decompile.js +46 -13
- package/dist/case/external.d.ts +27 -16
- package/dist/case/external.js +46 -19
- package/dist/case/in-arg-binding.d.ts +58 -0
- package/dist/case/in-arg-binding.js +48 -0
- package/dist/case/serialize.js +47 -24
- package/dist/core/refusals.d.ts +2 -1
- package/dist/core/refusals.js +2 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -437,9 +437,10 @@ export type ConnectorSpecData = Extract<ActionSpec, {
|
|
|
437
437
|
* and `hybrid` as `Async…Execution`, and writes the value itself into the task's
|
|
438
438
|
* `executionType` context.
|
|
439
439
|
*
|
|
440
|
-
* `hybrid`
|
|
441
|
-
*
|
|
442
|
-
*
|
|
440
|
+
* An `async` or `hybrid` object also answers through an event, which the
|
|
441
|
+
* Designer saves next to it; pass that pair as {@link ExternalTaskOptions.event}.
|
|
442
|
+
* A `hybrid` task without it is refused (EXTERNAL_TASK_EVENT), since the task
|
|
443
|
+
* would have nothing to wait on.
|
|
443
444
|
*
|
|
444
445
|
* It does not decide whether the stage waits for the task. A task the stage must
|
|
445
446
|
* not wait on is `.required(false)`, whatever its execution type. For example, a
|
|
@@ -447,6 +448,27 @@ export type ConnectorSpecData = Extract<ActionSpec, {
|
|
|
447
448
|
* it.
|
|
448
449
|
*/
|
|
449
450
|
export type ExternalExecutionMode = 'sync' | 'async' | 'hybrid';
|
|
451
|
+
/**
|
|
452
|
+
* The event an `async` or `hybrid` external object answers with, as Integration
|
|
453
|
+
* Service reports it for the connector object.
|
|
454
|
+
*
|
|
455
|
+
* @remarks
|
|
456
|
+
* Like the execution type, a property of the connector object, not a choice:
|
|
457
|
+
* the Case Designer reads it from the object's metadata (its first event mode
|
|
458
|
+
* and that mode's first event type) and saves it as the task's `operation` and
|
|
459
|
+
* `eventMode` context rows, and as `eventType` / `eventMode` in the metadata's
|
|
460
|
+
* `activityPropertyConfiguration`. Copy it from the same two rows of the
|
|
461
|
+
* activity's `case spec` result (`caseShape.context`). `decompile` lifts it from
|
|
462
|
+
* a saved task. For
|
|
463
|
+
* example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
|
|
464
|
+
* `{ operation: 'TRIGGER_CREATED', mode: 'polling' }`.
|
|
465
|
+
*/
|
|
466
|
+
export interface ExternalEventPair {
|
|
467
|
+
/** The event operation, e.g. `TRIGGER_CREATED`: the task's `operation` context row. */
|
|
468
|
+
operation: string;
|
|
469
|
+
/** How the event is received: the task's `eventMode` context row. */
|
|
470
|
+
mode: 'polling' | 'webhooks';
|
|
471
|
+
}
|
|
450
472
|
/** Required wiring and typed inputs for an external agent/workflow invocation. */
|
|
451
473
|
export interface ExternalTaskOptions<I extends Record<string, unknown>> {
|
|
452
474
|
/** Symbolic Integration Service connection name declared in `bindings.json`. */
|
|
@@ -460,6 +482,12 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
|
|
|
460
482
|
* `.required(false)`. See {@link ExternalExecutionMode}.
|
|
461
483
|
*/
|
|
462
484
|
mode: ExternalExecutionMode;
|
|
485
|
+
/**
|
|
486
|
+
* The event an `async` or `hybrid` object answers with. Required for `hybrid`;
|
|
487
|
+
* optional for `async`; refused for `sync`, which answers inline. See
|
|
488
|
+
* {@link ExternalEventPair}.
|
|
489
|
+
*/
|
|
490
|
+
event?: ExternalEventPair;
|
|
463
491
|
/** Inputs statically checked by the generated connector descriptor. */
|
|
464
492
|
inputs: I;
|
|
465
493
|
}
|
|
@@ -467,6 +495,8 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
|
|
|
467
495
|
export interface ExternalTaskSpecData {
|
|
468
496
|
/** The connector object's `executionType`. */
|
|
469
497
|
mode: ExternalExecutionMode;
|
|
498
|
+
/** The event an `async` or `hybrid` object answers with, when given. */
|
|
499
|
+
event?: ExternalEventPair;
|
|
470
500
|
/** Shared connector action identity, inputs, and symbolic bindings. */
|
|
471
501
|
connector: ConnectorSpecData;
|
|
472
502
|
}
|
|
@@ -683,9 +713,10 @@ export interface CaseVarDecl {
|
|
|
683
713
|
body?: unknown;
|
|
684
714
|
/**
|
|
685
715
|
* In-args only: the trigger this argument is bound to (its value arrives when
|
|
686
|
-
* that trigger fires). Set via `.input(shape, { from })`.
|
|
687
|
-
*
|
|
688
|
-
*
|
|
716
|
+
* that trigger fires). Set via `.input(shape, { from })`. Every In-arg emits the
|
|
717
|
+
* full three-entry binding (formal slot + companion + trigger-output bridge):
|
|
718
|
+
* when this is absent the arg binds to the primary trigger, and a case with
|
|
719
|
+
* none refuses it (CASE_IN_ARG_UNBOUND). See `.input()`.
|
|
689
720
|
*/
|
|
690
721
|
sourceTrigger?: BuiltTrigger;
|
|
691
722
|
}
|
|
@@ -1431,10 +1462,28 @@ declare class CaseBuilder {
|
|
|
1431
1462
|
layout(layout: CaseLayout): this;
|
|
1432
1463
|
/**
|
|
1433
1464
|
* Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
|
|
1434
|
-
* to set a default.
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1465
|
+
* to set a default. An In-arg is always bound to a trigger: its value arrives
|
|
1466
|
+
* when that trigger starts the case, it is readable as `=vars.<name>` (its
|
|
1467
|
+
* `inputOutputs` companion, keyed to the trigger, resolves it), and it is
|
|
1468
|
+
* projected into that trigger's `entry-points.json` input schema.
|
|
1469
|
+
*
|
|
1470
|
+
* Which trigger:
|
|
1471
|
+
* - `{ from: <trigger> }` binds the args to that trigger.
|
|
1472
|
+
* - Without `{ from }`, they bind to the primary trigger. That is the case's
|
|
1473
|
+
* only trigger (the one added with `.trigger()`, or the implicit manual
|
|
1474
|
+
* trigger when none was added), or, when there are several, the one that is
|
|
1475
|
+
* not a timer. `uip maestro case sdd convert` reads a blank
|
|
1476
|
+
* `sourceTriggers` the same way.
|
|
1477
|
+
* - Without `{ from }` in a case with no primary trigger (several non-timer
|
|
1478
|
+
* triggers, or only timers), `check` and `compile` refuse the plan with
|
|
1479
|
+
* `CASE_IN_ARG_UNBOUND`. Pass `{ from }`, or use `.var()` for a value no
|
|
1480
|
+
* trigger supplies.
|
|
1481
|
+
*
|
|
1482
|
+
* @example
|
|
1483
|
+
* **Declare an In-arg on the primary trigger (here the implicit manual one)**
|
|
1484
|
+
* ```ts
|
|
1485
|
+
* casePlan('x').input({ invoiceNumber: { type: 'string', default: 'INV-1042' } })
|
|
1486
|
+
* ```
|
|
1438
1487
|
*
|
|
1439
1488
|
* @example
|
|
1440
1489
|
* **Bind case In-args to a trigger's payload**
|
|
@@ -1445,7 +1494,8 @@ declare class CaseBuilder {
|
|
|
1445
1494
|
* ```
|
|
1446
1495
|
*
|
|
1447
1496
|
* @param shape - In-arg names to types, or `{ type, default }`.
|
|
1448
|
-
* @param opts - `{ from: <trigger> }` binds the args to
|
|
1497
|
+
* @param opts - `{ from: <trigger> }` binds the args to that trigger. Required
|
|
1498
|
+
* when the case has no primary trigger.
|
|
1449
1499
|
* @returns This builder, so calls chain.
|
|
1450
1500
|
*/
|
|
1451
1501
|
input(shape: Record<string, TypeDesc | JsonSchemaType | {
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -334,6 +334,7 @@ class TaskBuilder {
|
|
|
334
334
|
this.setKind(kind);
|
|
335
335
|
this._external = {
|
|
336
336
|
mode: opts.mode,
|
|
337
|
+
...(opts.event !== undefined ? { event: { ...opts.event } } : {}),
|
|
337
338
|
connector: makeConnector(descriptor, opts.inputs, {
|
|
338
339
|
connection: opts.connection,
|
|
339
340
|
folder: opts.folder,
|
|
@@ -955,10 +956,28 @@ class CaseBuilder {
|
|
|
955
956
|
}
|
|
956
957
|
/**
|
|
957
958
|
* Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
|
|
958
|
-
* to set a default.
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
959
|
+
* to set a default. An In-arg is always bound to a trigger: its value arrives
|
|
960
|
+
* when that trigger starts the case, it is readable as `=vars.<name>` (its
|
|
961
|
+
* `inputOutputs` companion, keyed to the trigger, resolves it), and it is
|
|
962
|
+
* projected into that trigger's `entry-points.json` input schema.
|
|
963
|
+
*
|
|
964
|
+
* Which trigger:
|
|
965
|
+
* - `{ from: <trigger> }` binds the args to that trigger.
|
|
966
|
+
* - Without `{ from }`, they bind to the primary trigger. That is the case's
|
|
967
|
+
* only trigger (the one added with `.trigger()`, or the implicit manual
|
|
968
|
+
* trigger when none was added), or, when there are several, the one that is
|
|
969
|
+
* not a timer. `uip maestro case sdd convert` reads a blank
|
|
970
|
+
* `sourceTriggers` the same way.
|
|
971
|
+
* - Without `{ from }` in a case with no primary trigger (several non-timer
|
|
972
|
+
* triggers, or only timers), `check` and `compile` refuse the plan with
|
|
973
|
+
* `CASE_IN_ARG_UNBOUND`. Pass `{ from }`, or use `.var()` for a value no
|
|
974
|
+
* trigger supplies.
|
|
975
|
+
*
|
|
976
|
+
* @example
|
|
977
|
+
* **Declare an In-arg on the primary trigger (here the implicit manual one)**
|
|
978
|
+
* ```ts
|
|
979
|
+
* casePlan('x').input({ invoiceNumber: { type: 'string', default: 'INV-1042' } })
|
|
980
|
+
* ```
|
|
962
981
|
*
|
|
963
982
|
* @example
|
|
964
983
|
* **Bind case In-args to a trigger's payload**
|
|
@@ -969,7 +988,8 @@ class CaseBuilder {
|
|
|
969
988
|
* ```
|
|
970
989
|
*
|
|
971
990
|
* @param shape - In-arg names to types, or `{ type, default }`.
|
|
972
|
-
* @param opts - `{ from: <trigger> }` binds the args to
|
|
991
|
+
* @param opts - `{ from: <trigger> }` binds the args to that trigger. Required
|
|
992
|
+
* when the case has no primary trigger.
|
|
973
993
|
* @returns This builder, so calls chain.
|
|
974
994
|
*/
|
|
975
995
|
input(shape, opts = {}) {
|
package/dist/case/check.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { businessEventProblems } from './business-event-shape.js';
|
|
2
|
+
import { isUnboundInArg, unboundInArgMessage } from './in-arg-binding.js';
|
|
2
3
|
import { ruleShapeProblems } from './rule-options.js';
|
|
3
4
|
import { types } from '../core/expr.js';
|
|
4
5
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, authoredCaseVersion, caseVersionMismatch } from './format-profile.js';
|
|
@@ -6,7 +7,7 @@ import { schemaVersionRefusal } from '../schema-version.js';
|
|
|
6
7
|
import { caseJsonPreservation, caseJsonPreservationErrors, preservedBindings } from './preserve.js';
|
|
7
8
|
import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
|
|
8
9
|
import { checkEventResolution } from '../core/event-checks.js';
|
|
9
|
-
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT,
|
|
10
|
+
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_EVENT, externalEventRefusal, isExternalExecutionMode } from './external.js';
|
|
10
11
|
import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
|
|
11
12
|
import { taskShapeProblems } from './task-shape.js';
|
|
12
13
|
import { isConnectorPlaceholder } from './connector-placeholder.js';
|
|
@@ -279,6 +280,11 @@ export function check(built, opts = {}) {
|
|
|
279
280
|
diags.push({ level: 'error', code: 'UNKNOWN_TASK_REF', message: `Rule "selected-tasks-completed" references unknown task "${n}".`, where });
|
|
280
281
|
}
|
|
281
282
|
};
|
|
283
|
+
// An In-arg with no `{ from }` binds to the primary trigger; when the case has
|
|
284
|
+
// none, `serialize` refuses it with the same code (in-arg-binding.ts).
|
|
285
|
+
for (const vd of built.vars)
|
|
286
|
+
if (isUnboundInArg(vd, built.triggers))
|
|
287
|
+
diags.push({ level: 'error', code: 'CASE_IN_ARG_UNBOUND', message: unboundInArgMessage(vd.name, built.triggers), where: vd.name });
|
|
282
288
|
// ── `=js:vars.<name>` resolvability — REMOVED, superseded by #257 ───────────
|
|
283
289
|
// This file briefly carried a VAR_NOT_REFERENCEABLE error asserting that only a
|
|
284
290
|
// trigger-bound In-arg (`.input(shape, { from })`) could be read as `=js:vars.x`,
|
|
@@ -623,13 +629,11 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
623
629
|
message: `${family} task "${t.name}" mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
|
|
624
630
|
where: t.name,
|
|
625
631
|
});
|
|
626
|
-
else
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
code:
|
|
630
|
-
|
|
631
|
-
where: t.name,
|
|
632
|
-
});
|
|
632
|
+
else {
|
|
633
|
+
const refused = externalEventRefusal(`${family} task "${t.name}"`, t.external.mode, t.external.event);
|
|
634
|
+
if (refused)
|
|
635
|
+
diags.push({ level: 'error', code: EXTERNAL_TASK_EVENT, message: refused, where: t.name });
|
|
636
|
+
}
|
|
633
637
|
if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
634
638
|
diags.push({
|
|
635
639
|
level: 'error',
|
package/dist/case/decompile.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { commentText } from '../core/comment-text.js';
|
|
2
|
+
import { implicitInArgTrigger } from './in-arg-binding.js';
|
|
2
3
|
import { tsLiteral, tsNumber } from '../core/ts-literal.js';
|
|
3
4
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
4
|
-
import { EXTERNAL_TASK_CONTEXT, externalDescriptorRefusal,
|
|
5
|
+
import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
|
|
5
6
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
6
7
|
import { ownEntry } from '../core/own-entry.js';
|
|
7
8
|
import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
|
|
@@ -618,7 +619,9 @@ function connectorCall(task, refs, library) {
|
|
|
618
619
|
/**
|
|
619
620
|
* Lift an External Agent / External Workflow task saved in the Case Designer's
|
|
620
621
|
* shape (the one `serialize` emits): a connector activity's context and input
|
|
621
|
-
* containers plus the object's `executionType`, which becomes `mode
|
|
622
|
+
* containers plus the object's `executionType`, which becomes `mode`, and the
|
|
623
|
+
* event pair an async / hybrid object answers with (`operation` and `eventMode`
|
|
624
|
+
* rows), which becomes `event`. The
|
|
622
625
|
* service type must be the one that execution type gives, so a plan whose two
|
|
623
626
|
* disagree is refused rather than lifted to a source that would compile to
|
|
624
627
|
* something else.
|
|
@@ -637,19 +640,20 @@ function externalCall(task, refs, library) {
|
|
|
637
640
|
throw new Error(`${owner} data.bindings must be an empty array; bindings live at case root.`);
|
|
638
641
|
if (!Array.isArray(data.context))
|
|
639
642
|
throw new Error(`${owner} requires a resolved context array.`);
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
throw new Error(externalHybridRefusal(owner));
|
|
644
|
-
// Exactly the context rows serialize writes (the Designer's set): an unknown
|
|
645
|
-
// or repeated row would be dropped by the lift and lost on recompile.
|
|
643
|
+
// Exactly the context rows serialize writes (the Designer's set, plus the
|
|
644
|
+
// event pair an async / hybrid object answers with): an unknown or repeated
|
|
645
|
+
// row would be dropped by the lift and lost on recompile.
|
|
646
646
|
const contextNames = data.context.map((row) => String(row?.name ?? ''));
|
|
647
|
-
const unknownContext = contextNames.find((name, at) => !EXTERNAL_TASK_CONTEXT.includes(name) || contextNames.indexOf(name) !== at);
|
|
647
|
+
const unknownContext = contextNames.find((name, at) => !(EXTERNAL_TASK_CONTEXT.includes(name) || EXTERNAL_TASK_EVENT_CONTEXT.includes(name)) || contextNames.indexOf(name) !== at);
|
|
648
648
|
if (unknownContext !== undefined)
|
|
649
649
|
throw new Error(`${owner} has unsupported or repeated context row "${unknownContext}".`);
|
|
650
650
|
const absentContext = EXTERNAL_TASK_CONTEXT.find((name) => !contextNames.includes(name));
|
|
651
651
|
if (absentContext)
|
|
652
652
|
throw new Error(`${owner} is missing context row "${absentContext}".`);
|
|
653
|
+
// The pair is written whole or not at all, so half of one cannot be lifted.
|
|
654
|
+
const eventRows = EXTERNAL_TASK_EVENT_CONTEXT.filter((name) => contextNames.includes(name));
|
|
655
|
+
if (eventRows.length === 1)
|
|
656
|
+
throw new Error(`${owner} carries context row "${eventRows[0]}" without "${EXTERNAL_TASK_EVENT_CONTEXT.find((name) => name !== eventRows[0])}"; the event pair is saved whole.`);
|
|
653
657
|
const context = contextMap(data);
|
|
654
658
|
const stringValue = (name) => {
|
|
655
659
|
const row = context.get(name);
|
|
@@ -672,6 +676,18 @@ function externalCall(task, refs, library) {
|
|
|
672
676
|
const serviceType = externalServiceType(kind, mode);
|
|
673
677
|
if (data.serviceType !== serviceType)
|
|
674
678
|
throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
|
|
679
|
+
// The event the object answers with, as saved: the same rule compile applies,
|
|
680
|
+
// so a lifted task always compiles back (a hybrid task without one does not).
|
|
681
|
+
const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
|
|
682
|
+
// A saved plan has no `event` option to drop, so a sync task with event rows
|
|
683
|
+
// gets its own text naming the rows; every other case is compile's rule.
|
|
684
|
+
if (mode === 'sync' && event)
|
|
685
|
+
throw new Error(`${owner} has context rows "operation" (${JSON.stringify(event.operation)}) and "eventMode" ` +
|
|
686
|
+
`(${JSON.stringify(event.mode)}) on executionType "sync"; a sync object answers inline and has no event. ` +
|
|
687
|
+
`Remove the two rows, or fix executionType and serviceType if the object is async or hybrid.`);
|
|
688
|
+
const eventRefused = externalEventRefusal(owner, mode, event);
|
|
689
|
+
if (eventRefused)
|
|
690
|
+
throw new Error(eventRefused);
|
|
675
691
|
const identity = library.identifyAction({
|
|
676
692
|
connectorKey: stringValue('connectorKey'),
|
|
677
693
|
httpMethod: stringValue('method'),
|
|
@@ -691,6 +707,7 @@ function externalCall(task, refs, library) {
|
|
|
691
707
|
['connection', tsLiteral(binding('connection'))],
|
|
692
708
|
['folder', tsLiteral(binding('folderKey'))],
|
|
693
709
|
['mode', tsLiteral(mode)],
|
|
710
|
+
['event', event ? opts([['operation', tsLiteral(event.operation)], ['mode', tsLiteral(event.mode)]]) : undefined],
|
|
694
711
|
['inputs', tsLiteral(inputs)],
|
|
695
712
|
]);
|
|
696
713
|
const method = kind === 'external-agent' ? 'externalAgent' : 'externalWorkflow';
|
|
@@ -1072,13 +1089,25 @@ function reassignedOwnNameTargets(plan) {
|
|
|
1072
1089
|
}
|
|
1073
1090
|
return targets;
|
|
1074
1091
|
}
|
|
1075
|
-
function emitVariables(out, depth, plan, triggerNameById) {
|
|
1092
|
+
function emitVariables(out, depth, plan, triggerNameById, triggerIsTimer) {
|
|
1076
1093
|
const variables = plan.variables ?? {};
|
|
1077
1094
|
const produced = producedVariableNames(plan);
|
|
1078
1095
|
for (const input of variables.inputs ?? []) {
|
|
1079
|
-
|
|
1096
|
+
let from = input.elementId && triggerNameById.get(input.elementId);
|
|
1097
|
+
// An In-arg with no trigger is a shape only older SDKs wrote (builder-sdk
|
|
1098
|
+
// #1368): no companion, so nothing binds it. A bare `.input()` now binds to
|
|
1099
|
+
// the primary trigger, which is what this source recompiles to. When the
|
|
1100
|
+
// case has no primary trigger a bare `.input()` is refused
|
|
1101
|
+
// (CASE_IN_ARG_UNBOUND), so the source names the first trigger and says so.
|
|
1102
|
+
if (!input.elementId && implicitInArgTrigger(triggerIsTimer) === undefined) {
|
|
1103
|
+
from = triggerNameById.values().next().value;
|
|
1104
|
+
out.line(depth, `// The source document left In-arg "${commentText(input.name)}" unbound and the case has no primary trigger; it is bound to the first trigger here (builder-sdk#1368).`);
|
|
1105
|
+
}
|
|
1080
1106
|
const fromOpts = from ? `, { from: ${from} }` : '';
|
|
1081
|
-
|
|
1107
|
+
// Compile writes `default: ""` on every In-arg that authored none, so the
|
|
1108
|
+
// empty default is not authored content and the source leaves it out.
|
|
1109
|
+
const spec = varSpec(input.default === '' ? { ...input, default: undefined } : input);
|
|
1110
|
+
out.line(depth, `.input({ ${key(input.name)}: ${spec} }${fromOpts})`);
|
|
1082
1111
|
}
|
|
1083
1112
|
for (const output of variables.outputs ?? [])
|
|
1084
1113
|
out.line(depth, `.output({ ${key(output.name)}: ${varSpec(output)} })`);
|
|
@@ -1114,6 +1143,10 @@ function bridgeRows(plan, triggerId, bag) {
|
|
|
1114
1143
|
}
|
|
1115
1144
|
return bridges;
|
|
1116
1145
|
}
|
|
1146
|
+
function isTimerTrigger(node) {
|
|
1147
|
+
const serviceType = triggerData(node).bag?.serviceType;
|
|
1148
|
+
return serviceType === 'Intsvc.TimerTrigger' || serviceType === 'timer';
|
|
1149
|
+
}
|
|
1117
1150
|
function triggerSource(node, index, plan, refs) {
|
|
1118
1151
|
const { label, description, bag } = triggerData(node);
|
|
1119
1152
|
const name = label !== `Trigger ${index + 1}` ? tsLiteral(label) : undefined;
|
|
@@ -1561,7 +1594,7 @@ export function decompileWithDiagnostics(plan, options = {}) {
|
|
|
1561
1594
|
throw new Error('allowAdhocOptionalStageTasks must be boolean.');
|
|
1562
1595
|
out.line(depth, metadata.allowAdhocOptionalStageTasks ? '.allowAdhocTasks()' : '.allowAdhocTasks(false)');
|
|
1563
1596
|
}
|
|
1564
|
-
emitVariables(out, depth, plan, triggerNameById);
|
|
1597
|
+
emitVariables(out, depth, plan, triggerNameById, triggers.map(isTimerTrigger));
|
|
1565
1598
|
triggers.forEach((_trigger, index) => out.line(depth, `.trigger(trigger${index + 1})`));
|
|
1566
1599
|
(metadata.slaRules ?? []).forEach((sla, index) => out.line(depth, `.sla(${slaSource(sla, index)})`));
|
|
1567
1600
|
for (const event of metadata.businessEvents ?? [])
|
package/dist/case/external.d.ts
CHANGED
|
@@ -8,32 +8,43 @@
|
|
|
8
8
|
* `buildConnectorInputs`, which `check` already runs as `connectorInputs`.
|
|
9
9
|
*/
|
|
10
10
|
import type { ResolvedConnector } from '../core/library.js';
|
|
11
|
-
import type { ExternalExecutionMode } from './case-sdk.js';
|
|
11
|
+
import type { ExternalEventPair, ExternalExecutionMode } from './case-sdk.js';
|
|
12
12
|
/**
|
|
13
13
|
* The context rows `serialize` writes on an external task: the set the Case
|
|
14
14
|
* Designer saves for a sync object (`uip`'s
|
|
15
15
|
* `tests/fixtures/external-tasks/designer-saved.json`, the Snowflake Cortex
|
|
16
|
-
* agent). `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* agent). An `async` or `hybrid` task may also carry
|
|
17
|
+
* {@link EXTERNAL_TASK_EVENT_CONTEXT}. `decompile` refuses a task with any other
|
|
18
|
+
* row, since the lift would drop it without a word.
|
|
19
19
|
*/
|
|
20
20
|
export declare const EXTERNAL_TASK_CONTEXT: readonly string[];
|
|
21
|
-
/** Check's code for {@link externalHybridRefusal}. */
|
|
22
|
-
export declare const EXTERNAL_TASK_HYBRID_UNSUPPORTED = "EXTERNAL_TASK_HYBRID_UNSUPPORTED";
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
* The two context rows that carry the event an `async` / `hybrid` object
|
|
23
|
+
* answers with (`ExternalEventPair`): `operation` (e.g. `TRIGGER_CREATED`) and
|
|
24
|
+
* `eventMode` (e.g. `polling`). The Designer writes them after `objectName`,
|
|
25
|
+
* before `executionType` (the Power Automate flow in `designer-saved.json`).
|
|
26
|
+
*/
|
|
27
|
+
export declare const EXTERNAL_TASK_EVENT_CONTEXT: readonly string[];
|
|
28
|
+
/** Check's and compile's code for {@link externalEventRefusal}. */
|
|
29
|
+
export declare const EXTERNAL_TASK_EVENT = "EXTERNAL_TASK_EVENT";
|
|
30
|
+
/** The event modes Integration Service reports for an object's events. */
|
|
31
|
+
export declare function isExternalEventMode(value: unknown): value is ExternalEventPair['mode'];
|
|
32
|
+
/**
|
|
33
|
+
* Why this task's event pair cannot be written, or `undefined`.
|
|
34
|
+
*
|
|
35
|
+
* A `hybrid` object answers through an event, and the Case Designer saves that
|
|
36
|
+
* event with the task (context `operation` and `eventMode`, and the same pair in
|
|
37
|
+
* the metadata's `activityPropertyConfiguration`). Without it the task has
|
|
38
|
+
* nothing to wait on, so `hybrid` needs the pair. An `async` object takes it
|
|
39
|
+
* when given. A `sync` object answers inline and has none, so a pair there is a
|
|
40
|
+
* wrong `mode` or a wrong pair. `check`, `serialize` and `decompile` all use this.
|
|
32
41
|
*
|
|
33
42
|
* @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
|
|
34
|
-
* @
|
|
43
|
+
* @param mode - The task's execution type (already known to be valid).
|
|
44
|
+
* @param event - The authored or saved pair, if any.
|
|
45
|
+
* @returns The refusal, or `undefined`.
|
|
35
46
|
*/
|
|
36
|
-
export declare function
|
|
47
|
+
export declare function externalEventRefusal(owner: string, mode: ExternalExecutionMode, event: unknown): string | undefined;
|
|
37
48
|
/** The execution types an external task accepts: the values Integration Service reports. */
|
|
38
49
|
export declare function isExternalExecutionMode(value: unknown): value is ExternalExecutionMode;
|
|
39
50
|
/**
|
package/dist/case/external.js
CHANGED
|
@@ -3,34 +3,61 @@ import { CASE_FORMAT_PROFILE } from './format-profile.js';
|
|
|
3
3
|
* The context rows `serialize` writes on an external task: the set the Case
|
|
4
4
|
* Designer saves for a sync object (`uip`'s
|
|
5
5
|
* `tests/fixtures/external-tasks/designer-saved.json`, the Snowflake Cortex
|
|
6
|
-
* agent). `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* agent). An `async` or `hybrid` task may also carry
|
|
7
|
+
* {@link EXTERNAL_TASK_EVENT_CONTEXT}. `decompile` refuses a task with any other
|
|
8
|
+
* row, since the lift would drop it without a word.
|
|
9
9
|
*/
|
|
10
10
|
export const EXTERNAL_TASK_CONTEXT = [
|
|
11
11
|
'connectorKey', 'connection', 'resourceKey', 'folderKey', 'objectName', 'method', 'path',
|
|
12
12
|
'executionType', 'metadata', 'activityConfigurationVersion',
|
|
13
13
|
];
|
|
14
|
-
/** Check's code for {@link externalHybridRefusal}. */
|
|
15
|
-
export const EXTERNAL_TASK_HYBRID_UNSUPPORTED = 'EXTERNAL_TASK_HYBRID_UNSUPPORTED';
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
* The two context rows that carry the event an `async` / `hybrid` object
|
|
16
|
+
* answers with (`ExternalEventPair`): `operation` (e.g. `TRIGGER_CREATED`) and
|
|
17
|
+
* `eventMode` (e.g. `polling`). The Designer writes them after `objectName`,
|
|
18
|
+
* before `executionType` (the Power Automate flow in `designer-saved.json`).
|
|
19
|
+
*/
|
|
20
|
+
export const EXTERNAL_TASK_EVENT_CONTEXT = ['operation', 'eventMode'];
|
|
21
|
+
/** Check's and compile's code for {@link externalEventRefusal}. */
|
|
22
|
+
export const EXTERNAL_TASK_EVENT = 'EXTERNAL_TASK_EVENT';
|
|
23
|
+
/** The event modes Integration Service reports for an object's events. */
|
|
24
|
+
export function isExternalEventMode(value) {
|
|
25
|
+
return value === 'polling' || value === 'webhooks';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Why this task's event pair cannot be written, or `undefined`.
|
|
29
|
+
*
|
|
30
|
+
* A `hybrid` object answers through an event, and the Case Designer saves that
|
|
31
|
+
* event with the task (context `operation` and `eventMode`, and the same pair in
|
|
32
|
+
* the metadata's `activityPropertyConfiguration`). Without it the task has
|
|
33
|
+
* nothing to wait on, so `hybrid` needs the pair. An `async` object takes it
|
|
34
|
+
* when given. A `sync` object answers inline and has none, so a pair there is a
|
|
35
|
+
* wrong `mode` or a wrong pair. `check`, `serialize` and `decompile` all use this.
|
|
25
36
|
*
|
|
26
37
|
* @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
|
|
27
|
-
* @
|
|
38
|
+
* @param mode - The task's execution type (already known to be valid).
|
|
39
|
+
* @param event - The authored or saved pair, if any.
|
|
40
|
+
* @returns The refusal, or `undefined`.
|
|
28
41
|
*/
|
|
29
|
-
export function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
export function externalEventRefusal(owner, mode, event) {
|
|
43
|
+
if (event === undefined) {
|
|
44
|
+
if (mode !== 'hybrid')
|
|
45
|
+
return undefined;
|
|
46
|
+
return (`${owner} is on a "hybrid" object, which answers through an event: pass the event the object reports as ` +
|
|
47
|
+
`event: { operation, mode } (for example { operation: "TRIGGER_CREATED", mode: "polling" }, ` +
|
|
48
|
+
`the "operation" and "eventMode" rows of the case spec result's caseShape.context). Without it the task has nothing to wait on.`);
|
|
49
|
+
}
|
|
50
|
+
if (mode === 'sync')
|
|
51
|
+
return `${owner} is on a "sync" object, which answers inline and has no event; drop event, or fix mode.`;
|
|
52
|
+
const pair = event;
|
|
53
|
+
if (typeof pair !== 'object' || pair === null || typeof pair.operation !== 'string' || !pair.operation.trim())
|
|
54
|
+
return `${owner} event.operation must be the object's non-empty event operation, e.g. "TRIGGER_CREATED".`;
|
|
55
|
+
// Written as given, and Integration Service matches it exactly: " TRIGGER_CREATED" would never match.
|
|
56
|
+
if (pair.operation !== pair.operation.trim())
|
|
57
|
+
return `${owner} event.operation ${JSON.stringify(pair.operation)} has surrounding whitespace; write it exactly as the object reports it, e.g. "TRIGGER_CREATED".`;
|
|
58
|
+
if (!isExternalEventMode(pair.mode))
|
|
59
|
+
return `${owner} event.mode must be "polling" or "webhooks"; got ${JSON.stringify(pair.mode)}.`;
|
|
60
|
+
return undefined;
|
|
34
61
|
}
|
|
35
62
|
/** The execution types an external task accepts: the values Integration Service reports. */
|
|
36
63
|
export function isExternalExecutionMode(value) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which trigger a case In-arg (`.input()`) binds to — the rule `check` reports,
|
|
3
|
+
* `serialize` applies and `decompile` follows, kept in one place so they cannot
|
|
4
|
+
* drift (builder-sdk#1368).
|
|
5
|
+
*
|
|
6
|
+
* An In-arg's value arrives from a trigger: the caseplan carries a formal slot
|
|
7
|
+
* in `variables.inputs[]`, a same-name `variables.inputOutputs[]` companion
|
|
8
|
+
* keyed to the trigger node, and a bridge on that node. That is the only shape
|
|
9
|
+
* the Case Designer writes, and `uip maestro case validate --strict` refuses an
|
|
10
|
+
* In-arg with no companion (STRICT_INPUTOUTPUTS_COMPANION). So an In-arg always
|
|
11
|
+
* binds:
|
|
12
|
+
*
|
|
13
|
+
* - `.input(shape, { from: t })` binds to `t`;
|
|
14
|
+
* - with no `{ from }`, it binds to the PRIMARY trigger: the case's only
|
|
15
|
+
* trigger (the one `.trigger()` added, or the implicit manual trigger when
|
|
16
|
+
* none was added, whatever its kind); with several, the one trigger that is
|
|
17
|
+
* not a timer — the one a caller starts the case through;
|
|
18
|
+
* - with no `{ from }` and no single such trigger (several non-timer
|
|
19
|
+
* triggers, or only timers) there is no right guess, and the case is
|
|
20
|
+
* refused with CASE_IN_ARG_UNBOUND.
|
|
21
|
+
*
|
|
22
|
+
* This is the rule `uip maestro case sdd convert` applies to an `In` row with a
|
|
23
|
+
* blank `sourceTriggers` cell (cli `sdd-convert/variables.ts`,
|
|
24
|
+
* `boundTriggerFor`), and the one its `in_arg_trigger_bind` grader checks.
|
|
25
|
+
*/
|
|
26
|
+
import type { BuiltTrigger, CaseVarDecl } from './case-sdk.js';
|
|
27
|
+
/**
|
|
28
|
+
* The index, among the triggers the case emits, of the one an In-arg without
|
|
29
|
+
* `{ from }` binds to.
|
|
30
|
+
*
|
|
31
|
+
* @param isTimer - For each emitted trigger, in order, whether it is a timer.
|
|
32
|
+
* An empty list stands for the implicit manual trigger.
|
|
33
|
+
* @returns The trigger's index, or `undefined` when no single trigger qualifies.
|
|
34
|
+
*/
|
|
35
|
+
export declare function implicitInArgTrigger(isTimer: readonly boolean[]): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* {@link implicitInArgTrigger} over a built case's declared triggers.
|
|
38
|
+
*
|
|
39
|
+
* @param triggers - The case's `.trigger()` list.
|
|
40
|
+
* @returns The index of the trigger a `{ from }`-less In-arg binds to, or `undefined`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function implicitInArgBuiltTrigger(triggers: readonly BuiltTrigger[]): number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Whether an In-arg declared without `{ from }` has no trigger to bind to.
|
|
45
|
+
*
|
|
46
|
+
* @param vd - The declared variable.
|
|
47
|
+
* @param triggers - The case's `.trigger()` list.
|
|
48
|
+
* @returns `true` when the In-arg must name its trigger with `{ from }`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isUnboundInArg(vd: CaseVarDecl, triggers: readonly BuiltTrigger[]): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The CASE_IN_ARG_UNBOUND message.
|
|
53
|
+
*
|
|
54
|
+
* @param name - The In-arg's name.
|
|
55
|
+
* @param triggers - The case's `.trigger()` list.
|
|
56
|
+
* @returns The message both `check` and `serialize` report.
|
|
57
|
+
*/
|
|
58
|
+
export declare function unboundInArgMessage(name: string, triggers: readonly BuiltTrigger[]): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The index, among the triggers the case emits, of the one an In-arg without
|
|
3
|
+
* `{ from }` binds to.
|
|
4
|
+
*
|
|
5
|
+
* @param isTimer - For each emitted trigger, in order, whether it is a timer.
|
|
6
|
+
* An empty list stands for the implicit manual trigger.
|
|
7
|
+
* @returns The trigger's index, or `undefined` when no single trigger qualifies.
|
|
8
|
+
*/
|
|
9
|
+
export function implicitInArgTrigger(isTimer) {
|
|
10
|
+
if (isTimer.length <= 1)
|
|
11
|
+
return 0;
|
|
12
|
+
const callable = isTimer.flatMap((timer, i) => (timer ? [] : [i]));
|
|
13
|
+
return callable.length === 1 ? callable[0] : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* {@link implicitInArgTrigger} over a built case's declared triggers.
|
|
17
|
+
*
|
|
18
|
+
* @param triggers - The case's `.trigger()` list.
|
|
19
|
+
* @returns The index of the trigger a `{ from }`-less In-arg binds to, or `undefined`.
|
|
20
|
+
*/
|
|
21
|
+
export function implicitInArgBuiltTrigger(triggers) {
|
|
22
|
+
return implicitInArgTrigger(triggers.map((t) => t.kind === 'timer'));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Whether an In-arg declared without `{ from }` has no trigger to bind to.
|
|
26
|
+
*
|
|
27
|
+
* @param vd - The declared variable.
|
|
28
|
+
* @param triggers - The case's `.trigger()` list.
|
|
29
|
+
* @returns `true` when the In-arg must name its trigger with `{ from }`.
|
|
30
|
+
*/
|
|
31
|
+
export function isUnboundInArg(vd, triggers) {
|
|
32
|
+
return vd.direction === 'in' && !vd.sourceTrigger && implicitInArgBuiltTrigger(triggers) === undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The CASE_IN_ARG_UNBOUND message.
|
|
36
|
+
*
|
|
37
|
+
* @param name - The In-arg's name.
|
|
38
|
+
* @param triggers - The case's `.trigger()` list.
|
|
39
|
+
* @returns The message both `check` and `serialize` report.
|
|
40
|
+
*/
|
|
41
|
+
export function unboundInArgMessage(name, triggers) {
|
|
42
|
+
const callable = triggers.filter((t) => t.kind !== 'timer').length;
|
|
43
|
+
const why = callable === 0
|
|
44
|
+
? `all ${triggers.length} of the case's triggers are timers`
|
|
45
|
+
: `${callable} of the case's ${triggers.length} triggers are not timers`;
|
|
46
|
+
return `In-arg "${name}" has no { from }, and ${why}, so there is no primary trigger to bind it to. `
|
|
47
|
+
+ `Pass .input({ ${name}: ... }, { from: <trigger> }) to name the trigger whose start supplies it, or declare it with .var() if no trigger supplies it.`;
|
|
48
|
+
}
|