@uipath/maestro-builder-sdk 6.68.5 → 6.68.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/dist/api-index.md +33 -33
- package/dist/api-members.md +158 -158
- package/dist/bpmn/intsvc.js +10 -0
- package/dist/case/case-sdk.d.ts +50 -25
- package/dist/case/case-sdk.js +25 -6
- package/dist/case/check.d.ts +19 -0
- package/dist/case/check.js +26 -15
- package/dist/case/cli-library.js +12 -1
- package/dist/case/decompile.js +37 -7
- package/dist/case/external.d.ts +46 -0
- package/dist/case/external.js +79 -1
- package/dist/case/in-arg-binding.d.ts +58 -0
- package/dist/case/in-arg-binding.js +48 -0
- package/dist/case/serialize.js +44 -28
- package/dist/core/refusals.d.ts +2 -0
- package/dist/core/refusals.js +2 -0
- package/dist/registry/library-build/enrich.d.ts +20 -0
- package/dist/registry/library-build/enrich.js +40 -0
- package/dist/registry/model/library.d.ts +10 -0
- package/package.json +2 -2
package/dist/bpmn/intsvc.js
CHANGED
|
@@ -191,6 +191,16 @@ export function createExternalTask(ctx, n) {
|
|
|
191
191
|
`${n.key}/${n.action} is "${String(op.subType ?? 'untyped')}". ` +
|
|
192
192
|
`The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
|
|
193
193
|
}
|
|
194
|
+
// The object's execution type, when the library records it (#1369): a `mode`
|
|
195
|
+
// that contradicts it compiles the other contract, which validate accepts and
|
|
196
|
+
// the run gets wrong. Studio Web runs `hybrid` as `async`.
|
|
197
|
+
const recorded = op.executionType;
|
|
198
|
+
if (recorded === 'sync' || recorded === 'async' || recorded === 'hybrid') {
|
|
199
|
+
const runs = recorded === 'sync' ? 'sync' : 'async';
|
|
200
|
+
if (n.mode !== runs)
|
|
201
|
+
throw new CompileRefusal('EXTERNAL_TASK_MODE_MISMATCH', `external ${n.family} "${n.id}" has mode "${n.mode}", but the connector library records ${n.key}/${n.action}'s ` +
|
|
202
|
+
`executionType as "${recorded}", which runs ${runs}; the node would run the wrong contract. Use mode: "${runs}".`);
|
|
203
|
+
}
|
|
194
204
|
const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
195
205
|
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
|
|
196
206
|
if (!conn || !folder) {
|
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -430,9 +430,12 @@ export type ConnectorSpecData = Extract<ActionSpec, {
|
|
|
430
430
|
* reports it for the connector object.
|
|
431
431
|
*
|
|
432
432
|
* @remarks
|
|
433
|
-
* This is a property of the connector object, not a choice about the case
|
|
434
|
-
* it
|
|
435
|
-
*
|
|
433
|
+
* This is a property of the connector object, not a choice about the case. The
|
|
434
|
+
* connector library records it for the object when it was built or prepared
|
|
435
|
+
* from Integration Service metadata that reports it, and compile then takes it
|
|
436
|
+
* from there: leave `mode` out. Otherwise copy it from the activity's `case spec`
|
|
437
|
+
* result (`Data.EssentialConfiguration.essentialConfiguration.executionType`).
|
|
438
|
+
* A `mode` the library contradicts is refused (EXTERNAL_TASK_MODE_MISMATCH). The Case
|
|
436
439
|
* Designer saves `sync` as the `Sync…Execution` service type and both `async`
|
|
437
440
|
* and `hybrid` as `Async…Execution`, and writes the value itself into the task's
|
|
438
441
|
* `executionType` context.
|
|
@@ -457,10 +460,10 @@ export type ExternalExecutionMode = 'sync' | 'async' | 'hybrid';
|
|
|
457
460
|
* the Case Designer reads it from the object's metadata (its first event mode
|
|
458
461
|
* and that mode's first event type) and saves it as the task's `operation` and
|
|
459
462
|
* `eventMode` context rows, and as `eventType` / `eventMode` in the metadata's
|
|
460
|
-
* `activityPropertyConfiguration`.
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
|
|
463
|
+
* `activityPropertyConfiguration`. Like `mode`, compile takes it from the
|
|
464
|
+
* connector library when the library records it; otherwise copy it from the same
|
|
465
|
+
* two rows of the activity's `case spec` result (`caseShape.context`).
|
|
466
|
+
* `decompile` lifts it from a saved task. For example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
|
|
464
467
|
* `{ operation: 'TRIGGER_CREATED', mode: 'polling' }`.
|
|
465
468
|
*/
|
|
466
469
|
export interface ExternalEventPair {
|
|
@@ -476,16 +479,18 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
|
|
|
476
479
|
/** Symbolic Orchestrator folder binding name declared in `bindings.json`. */
|
|
477
480
|
folder: string;
|
|
478
481
|
/**
|
|
479
|
-
* The connector object's `executionType
|
|
480
|
-
*
|
|
481
|
-
* `
|
|
482
|
+
* The connector object's `executionType`. Leave it out when the connector
|
|
483
|
+
* library records it (compile reads it from there); otherwise copy it from the
|
|
484
|
+
* `case spec` result. Compile refuses a task with no mode from either
|
|
485
|
+
* (EXTERNAL_TASK_MODE), and one the library contradicts
|
|
486
|
+
* (EXTERNAL_TASK_MODE_MISMATCH). Not whether the stage waits: that is
|
|
482
487
|
* `.required(false)`. See {@link ExternalExecutionMode}.
|
|
483
488
|
*/
|
|
484
|
-
mode
|
|
489
|
+
mode?: ExternalExecutionMode;
|
|
485
490
|
/**
|
|
486
|
-
* The event an `async` or `hybrid` object answers with.
|
|
487
|
-
*
|
|
488
|
-
* {@link ExternalEventPair}.
|
|
491
|
+
* The event an `async` or `hybrid` object answers with. Leave it out when the
|
|
492
|
+
* connector library records it. A `hybrid` task needs one from either place;
|
|
493
|
+
* a `sync` one refuses it, since it answers inline. See {@link ExternalEventPair}.
|
|
489
494
|
*/
|
|
490
495
|
event?: ExternalEventPair;
|
|
491
496
|
/** Inputs statically checked by the generated connector descriptor. */
|
|
@@ -493,9 +498,9 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
|
|
|
493
498
|
}
|
|
494
499
|
/** Normalized descriptor and execution mode stored on a built external task. */
|
|
495
500
|
export interface ExternalTaskSpecData {
|
|
496
|
-
/** The connector object's `executionType
|
|
497
|
-
mode
|
|
498
|
-
/** The event an `async` or `hybrid` object answers with, when
|
|
501
|
+
/** The connector object's `executionType`, when authored (else the library's). */
|
|
502
|
+
mode?: ExternalExecutionMode;
|
|
503
|
+
/** The event an `async` or `hybrid` object answers with, when authored (else the library's). */
|
|
499
504
|
event?: ExternalEventPair;
|
|
500
505
|
/** Shared connector action identity, inputs, and symbolic bindings. */
|
|
501
506
|
connector: ConnectorSpecData;
|
|
@@ -713,9 +718,10 @@ export interface CaseVarDecl {
|
|
|
713
718
|
body?: unknown;
|
|
714
719
|
/**
|
|
715
720
|
* In-args only: the trigger this argument is bound to (its value arrives when
|
|
716
|
-
* that trigger fires). Set via `.input(shape, { from })`.
|
|
717
|
-
*
|
|
718
|
-
*
|
|
721
|
+
* that trigger fires). Set via `.input(shape, { from })`. Every In-arg emits the
|
|
722
|
+
* full three-entry binding (formal slot + companion + trigger-output bridge):
|
|
723
|
+
* when this is absent the arg binds to the primary trigger, and a case with
|
|
724
|
+
* none refuses it (CASE_IN_ARG_UNBOUND). See `.input()`.
|
|
719
725
|
*/
|
|
720
726
|
sourceTrigger?: BuiltTrigger;
|
|
721
727
|
}
|
|
@@ -1461,10 +1467,28 @@ declare class CaseBuilder {
|
|
|
1461
1467
|
layout(layout: CaseLayout): this;
|
|
1462
1468
|
/**
|
|
1463
1469
|
* Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
|
|
1464
|
-
* to set a default.
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1470
|
+
* to set a default. An In-arg is always bound to a trigger: its value arrives
|
|
1471
|
+
* when that trigger starts the case, it is readable as `=vars.<name>` (its
|
|
1472
|
+
* `inputOutputs` companion, keyed to the trigger, resolves it), and it is
|
|
1473
|
+
* projected into that trigger's `entry-points.json` input schema.
|
|
1474
|
+
*
|
|
1475
|
+
* Which trigger:
|
|
1476
|
+
* - `{ from: <trigger> }` binds the args to that trigger.
|
|
1477
|
+
* - Without `{ from }`, they bind to the primary trigger. That is the case's
|
|
1478
|
+
* only trigger (the one added with `.trigger()`, or the implicit manual
|
|
1479
|
+
* trigger when none was added), or, when there are several, the one that is
|
|
1480
|
+
* not a timer. `uip maestro case sdd convert` reads a blank
|
|
1481
|
+
* `sourceTriggers` the same way.
|
|
1482
|
+
* - Without `{ from }` in a case with no primary trigger (several non-timer
|
|
1483
|
+
* triggers, or only timers), `check` and `compile` refuse the plan with
|
|
1484
|
+
* `CASE_IN_ARG_UNBOUND`. Pass `{ from }`, or use `.var()` for a value no
|
|
1485
|
+
* trigger supplies.
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* **Declare an In-arg on the primary trigger (here the implicit manual one)**
|
|
1489
|
+
* ```ts
|
|
1490
|
+
* casePlan('x').input({ invoiceNumber: { type: 'string', default: 'INV-1042' } })
|
|
1491
|
+
* ```
|
|
1468
1492
|
*
|
|
1469
1493
|
* @example
|
|
1470
1494
|
* **Bind case In-args to a trigger's payload**
|
|
@@ -1475,7 +1499,8 @@ declare class CaseBuilder {
|
|
|
1475
1499
|
* ```
|
|
1476
1500
|
*
|
|
1477
1501
|
* @param shape - In-arg names to types, or `{ type, default }`.
|
|
1478
|
-
* @param opts - `{ from: <trigger> }` binds the args to
|
|
1502
|
+
* @param opts - `{ from: <trigger> }` binds the args to that trigger. Required
|
|
1503
|
+
* when the case has no primary trigger.
|
|
1479
1504
|
* @returns This builder, so calls chain.
|
|
1480
1505
|
*/
|
|
1481
1506
|
input(shape: Record<string, TypeDesc | JsonSchemaType | {
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -333,7 +333,7 @@ class TaskBuilder {
|
|
|
333
333
|
external(kind, descriptor, opts) {
|
|
334
334
|
this.setKind(kind);
|
|
335
335
|
this._external = {
|
|
336
|
-
mode: opts.mode,
|
|
336
|
+
...(opts.mode !== undefined ? { mode: opts.mode } : {}),
|
|
337
337
|
...(opts.event !== undefined ? { event: { ...opts.event } } : {}),
|
|
338
338
|
connector: makeConnector(descriptor, opts.inputs, {
|
|
339
339
|
connection: opts.connection,
|
|
@@ -956,10 +956,28 @@ class CaseBuilder {
|
|
|
956
956
|
}
|
|
957
957
|
/**
|
|
958
958
|
* Declare case In-args. Each value is a {@link TypeDesc}, or `{ type, default }`
|
|
959
|
-
* to set a default.
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
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
|
+
* ```
|
|
963
981
|
*
|
|
964
982
|
* @example
|
|
965
983
|
* **Bind case In-args to a trigger's payload**
|
|
@@ -970,7 +988,8 @@ class CaseBuilder {
|
|
|
970
988
|
* ```
|
|
971
989
|
*
|
|
972
990
|
* @param shape - In-arg names to types, or `{ type, default }`.
|
|
973
|
-
* @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.
|
|
974
993
|
* @returns This builder, so calls chain.
|
|
975
994
|
*/
|
|
976
995
|
input(shape, opts = {}) {
|
package/dist/case/check.d.ts
CHANGED
|
@@ -52,6 +52,25 @@ export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | '
|
|
|
52
52
|
version?: string;
|
|
53
53
|
object?: string;
|
|
54
54
|
}) => string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* What Case `compile` would refuse an External Agent / External Workflow
|
|
57
|
+
* task's mode and event with, judged against what the library records for
|
|
58
|
+
* the object (`externalExecution`): EXTERNAL_TASK_MODE (no mode from the
|
|
59
|
+
* author or the library), EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT.
|
|
60
|
+
* `undefined` when they are fine, or the descriptor does not resolve.
|
|
61
|
+
*/
|
|
62
|
+
externalExecutionError?: (owner: string, descriptor: {
|
|
63
|
+
key: string;
|
|
64
|
+
action: string;
|
|
65
|
+
version?: string;
|
|
66
|
+
object?: string;
|
|
67
|
+
}, authored: {
|
|
68
|
+
mode?: unknown;
|
|
69
|
+
event?: unknown;
|
|
70
|
+
}) => {
|
|
71
|
+
code: string;
|
|
72
|
+
message: string;
|
|
73
|
+
} | undefined;
|
|
55
74
|
subscriptionError?: (kind: 'trigger' | 'event', subscription: EventSubscription) => {
|
|
56
75
|
code: string;
|
|
57
76
|
message: string;
|
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, externalExecution } 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`,
|
|
@@ -297,7 +303,7 @@ export function check(built, opts = {}) {
|
|
|
297
303
|
// positive that blocks emit (this gate runs before serialization), so it must be
|
|
298
304
|
// re-derived from the serializer rather than from remembered probe results.
|
|
299
305
|
for (const stage of built.stages) {
|
|
300
|
-
checkStage(stage, diags, refStage, refPlacement, opts.bindings);
|
|
306
|
+
checkStage(stage, diags, refStage, refPlacement, opts.bindings, opts.externalExecutionError !== undefined);
|
|
301
307
|
stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
|
|
302
308
|
}
|
|
303
309
|
built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
|
|
@@ -319,7 +325,9 @@ export function check(built, opts = {}) {
|
|
|
319
325
|
* CONNECTOR_OBJECT_REQUIRED, CONNECTOR_OBJECT_NOT_GENERIC; then their inputs
|
|
320
326
|
* (`buildConnectorInputs`): CONNECTOR_INPUT, LOOKUP_UNRESOLVED;
|
|
321
327
|
* - an external task's resolved descriptor (`externalDescriptorRefusal`):
|
|
322
|
-
* EXTERNAL_TASK_DESCRIPTOR_CONTRACT;
|
|
328
|
+
* EXTERNAL_TASK_DESCRIPTOR_CONTRACT; and its mode and event against what the
|
|
329
|
+
* library records for the object (`externalExecution`): EXTERNAL_TASK_MODE,
|
|
330
|
+
* EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT;
|
|
323
331
|
* - an event trigger, a wait-for-connector task and a wait-for-connector rule
|
|
324
332
|
* resolve their subscription (`resolveCaseSubscription`): EVENT_UNKNOWN,
|
|
325
333
|
* EVENT_GENERIC_NO_OBJECT.
|
|
@@ -385,6 +393,9 @@ function checkLibraryConnectors(built, opts, diags) {
|
|
|
385
393
|
const refused = opts.externalDescriptorError?.(t.kind, spec);
|
|
386
394
|
if (refused !== undefined)
|
|
387
395
|
diags.push({ level: 'error', code: EXTERNAL_TASK_DESCRIPTOR_CONTRACT, message: `${where}: ${refused}`, where: t.name });
|
|
396
|
+
const execution = opts.externalExecutionError?.(where, spec, t.external);
|
|
397
|
+
if (execution !== undefined)
|
|
398
|
+
diags.push({ level: 'error', code: execution.code, message: execution.message, where: t.name });
|
|
388
399
|
}
|
|
389
400
|
if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
|
|
390
401
|
subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
|
|
@@ -435,7 +446,9 @@ function isNilOrEmpty(value) {
|
|
|
435
446
|
return Object.keys(value).length === 0;
|
|
436
447
|
return false;
|
|
437
448
|
}
|
|
438
|
-
function checkStage(stage, diags, refStage, refPlacement, bindings
|
|
449
|
+
function checkStage(stage, diags, refStage, refPlacement, bindings,
|
|
450
|
+
/** A library judges external tasks' mode and event (`externalExecutionError`). */
|
|
451
|
+
externalJudge) {
|
|
439
452
|
const where = `stage "${stage.label}"`;
|
|
440
453
|
if (stage.exception && stage.required !== false)
|
|
441
454
|
diags.push({
|
|
@@ -616,17 +629,15 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
616
629
|
where: t.name,
|
|
617
630
|
});
|
|
618
631
|
else {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (refused)
|
|
629
|
-
diags.push({ level: 'error', code: EXTERNAL_TASK_EVENT, message: refused, where: t.name });
|
|
632
|
+
// The authored mode and event alone. With a library at hand, the
|
|
633
|
+
// library pass judges them against the object instead (and reports a
|
|
634
|
+
// missing mode), so they are judged here only without one.
|
|
635
|
+
const libraryJudges = externalJudge
|
|
636
|
+
&& !!t.external.connector?.key?.trim() && !!t.external.connector?.action?.trim();
|
|
637
|
+
if (!libraryJudges) {
|
|
638
|
+
const judged = externalExecution(`${family} task "${t.name}"`, t.external, undefined);
|
|
639
|
+
if (judged?.code !== undefined)
|
|
640
|
+
diags.push({ level: 'error', code: judged.code, message: judged.message, where: t.name });
|
|
630
641
|
}
|
|
631
642
|
if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
|
|
632
643
|
diags.push({
|
package/dist/case/cli-library.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* both `case check` and `case compile`'s check pass call.
|
|
9
9
|
*/
|
|
10
10
|
import { connectorCheckOpts } from '../core/cli-library.js';
|
|
11
|
-
import { externalDescriptorRefusal } from './external.js';
|
|
11
|
+
import { externalDescriptorRefusal, externalExecution } from './external.js';
|
|
12
12
|
import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscription } from './subscription.js';
|
|
13
13
|
/**
|
|
14
14
|
* Case's check options from a library the CLI already opened, or `{}` without one.
|
|
@@ -48,6 +48,17 @@ export function caseCheckOpts(library, bindings) {
|
|
|
48
48
|
}
|
|
49
49
|
return externalDescriptorRefusal(kind, rc);
|
|
50
50
|
},
|
|
51
|
+
externalExecutionError: (owner, d, authored) => {
|
|
52
|
+
let rc;
|
|
53
|
+
try {
|
|
54
|
+
rc = library.resolve(d.key, d.action, d.version, d.object);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined; // connectorResolveError's to report
|
|
58
|
+
}
|
|
59
|
+
const judged = externalExecution(owner, authored, rc.entry?.operation ?? {});
|
|
60
|
+
return judged?.code !== undefined ? { code: judged.code, message: judged.message } : undefined;
|
|
61
|
+
},
|
|
51
62
|
subscriptionError: (kind, sub) => {
|
|
52
63
|
try {
|
|
53
64
|
resolveCaseSubscription(library, kind, sub);
|
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, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
|
|
5
|
+
import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode, libraryExecution } 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';
|
|
@@ -675,8 +676,8 @@ function externalCall(task, refs, library) {
|
|
|
675
676
|
const serviceType = externalServiceType(kind, mode);
|
|
676
677
|
if (data.serviceType !== serviceType)
|
|
677
678
|
throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
|
|
678
|
-
// The event the object answers with, as saved
|
|
679
|
-
//
|
|
679
|
+
// The event the object answers with, as saved, judged by compile's event rule
|
|
680
|
+
// here, and against the library's record below (`externalExecution` in compile).
|
|
680
681
|
const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
|
|
681
682
|
// A saved plan has no `event` option to drop, so a sync task with event rows
|
|
682
683
|
// gets its own text naming the rows; every other case is compile's rule.
|
|
@@ -698,6 +699,19 @@ function externalCall(task, refs, library) {
|
|
|
698
699
|
const refused = externalDescriptorRefusal(kind, resolved);
|
|
699
700
|
if (refused)
|
|
700
701
|
throw new Error(`${owner}: ${refused}`);
|
|
702
|
+
// Compile refuses a mode or event the library contradicts (#1369), so a task
|
|
703
|
+
// saved against a different record would lift to source that does not compile
|
|
704
|
+
// back: refuse it here, by name, before the author edits anything.
|
|
705
|
+
const recorded = libraryExecution(resolved.entry?.operation);
|
|
706
|
+
if (recorded.mode !== undefined && recorded.mode !== mode)
|
|
707
|
+
throw new Error(`${owner} is saved with executionType "${mode}", but the connector library records the object's ` +
|
|
708
|
+
`executionType as "${recorded.mode}", so the lifted source would not compile back. Re-save the task in Studio Web, ` +
|
|
709
|
+
`or rebuild the library (registry prepare) if the object changed.`);
|
|
710
|
+
if (event !== undefined && recorded.event !== undefined
|
|
711
|
+
&& (event.operation !== recorded.event.operation || event.mode !== recorded.event.mode))
|
|
712
|
+
throw new Error(`${owner} is saved with event ${JSON.stringify(event)}, but the connector library records the object's ` +
|
|
713
|
+
`event as ${JSON.stringify(recorded.event)}, so the lifted source would not compile back. Re-save the task in Studio Web, ` +
|
|
714
|
+
`or rebuild the library (registry prepare) if the object changed.`);
|
|
701
715
|
if (!Array.isArray(data.inputs))
|
|
702
716
|
throw new Error(`${owner} inputs must be an array.`);
|
|
703
717
|
const inputs = connectorInputs(data.inputs, resolved, owner);
|
|
@@ -1088,13 +1102,25 @@ function reassignedOwnNameTargets(plan) {
|
|
|
1088
1102
|
}
|
|
1089
1103
|
return targets;
|
|
1090
1104
|
}
|
|
1091
|
-
function emitVariables(out, depth, plan, triggerNameById) {
|
|
1105
|
+
function emitVariables(out, depth, plan, triggerNameById, triggerIsTimer) {
|
|
1092
1106
|
const variables = plan.variables ?? {};
|
|
1093
1107
|
const produced = producedVariableNames(plan);
|
|
1094
1108
|
for (const input of variables.inputs ?? []) {
|
|
1095
|
-
|
|
1109
|
+
let from = input.elementId && triggerNameById.get(input.elementId);
|
|
1110
|
+
// An In-arg with no trigger is a shape only older SDKs wrote (builder-sdk
|
|
1111
|
+
// #1368): no companion, so nothing binds it. A bare `.input()` now binds to
|
|
1112
|
+
// the primary trigger, which is what this source recompiles to. When the
|
|
1113
|
+
// case has no primary trigger a bare `.input()` is refused
|
|
1114
|
+
// (CASE_IN_ARG_UNBOUND), so the source names the first trigger and says so.
|
|
1115
|
+
if (!input.elementId && implicitInArgTrigger(triggerIsTimer) === undefined) {
|
|
1116
|
+
from = triggerNameById.values().next().value;
|
|
1117
|
+
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).`);
|
|
1118
|
+
}
|
|
1096
1119
|
const fromOpts = from ? `, { from: ${from} }` : '';
|
|
1097
|
-
|
|
1120
|
+
// Compile writes `default: ""` on every In-arg that authored none, so the
|
|
1121
|
+
// empty default is not authored content and the source leaves it out.
|
|
1122
|
+
const spec = varSpec(input.default === '' ? { ...input, default: undefined } : input);
|
|
1123
|
+
out.line(depth, `.input({ ${key(input.name)}: ${spec} }${fromOpts})`);
|
|
1098
1124
|
}
|
|
1099
1125
|
for (const output of variables.outputs ?? [])
|
|
1100
1126
|
out.line(depth, `.output({ ${key(output.name)}: ${varSpec(output)} })`);
|
|
@@ -1130,6 +1156,10 @@ function bridgeRows(plan, triggerId, bag) {
|
|
|
1130
1156
|
}
|
|
1131
1157
|
return bridges;
|
|
1132
1158
|
}
|
|
1159
|
+
function isTimerTrigger(node) {
|
|
1160
|
+
const serviceType = triggerData(node).bag?.serviceType;
|
|
1161
|
+
return serviceType === 'Intsvc.TimerTrigger' || serviceType === 'timer';
|
|
1162
|
+
}
|
|
1133
1163
|
function triggerSource(node, index, plan, refs) {
|
|
1134
1164
|
const { label, description, bag } = triggerData(node);
|
|
1135
1165
|
const name = label !== `Trigger ${index + 1}` ? tsLiteral(label) : undefined;
|
|
@@ -1577,7 +1607,7 @@ export function decompileWithDiagnostics(plan, options = {}) {
|
|
|
1577
1607
|
throw new Error('allowAdhocOptionalStageTasks must be boolean.');
|
|
1578
1608
|
out.line(depth, metadata.allowAdhocOptionalStageTasks ? '.allowAdhocTasks()' : '.allowAdhocTasks(false)');
|
|
1579
1609
|
}
|
|
1580
|
-
emitVariables(out, depth, plan, triggerNameById);
|
|
1610
|
+
emitVariables(out, depth, plan, triggerNameById, triggers.map(isTimerTrigger));
|
|
1581
1611
|
triggers.forEach((_trigger, index) => out.line(depth, `.trigger(trigger${index + 1})`));
|
|
1582
1612
|
(metadata.slaRules ?? []).forEach((sla, index) => out.line(depth, `.sla(${slaSource(sla, index)})`));
|
|
1583
1613
|
for (const event of metadata.businessEvents ?? [])
|
package/dist/case/external.d.ts
CHANGED
|
@@ -58,6 +58,52 @@ export declare function isExternalExecutionMode(value: unknown): value is Extern
|
|
|
58
58
|
* @returns The service type.
|
|
59
59
|
*/
|
|
60
60
|
export declare function externalServiceType(kind: 'external-agent' | 'external-workflow', mode: ExternalExecutionMode): string;
|
|
61
|
+
/** Check's and compile's code when an authored `mode` disagrees with the library. */
|
|
62
|
+
export declare const EXTERNAL_TASK_MODE_MISMATCH = "EXTERNAL_TASK_MODE_MISMATCH";
|
|
63
|
+
/** A judged execution: the task's mode and event, or why it cannot be written. */
|
|
64
|
+
export type ExternalExecution = {
|
|
65
|
+
mode: ExternalExecutionMode;
|
|
66
|
+
event?: ExternalEventPair;
|
|
67
|
+
code?: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
code: string;
|
|
70
|
+
message: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The execution facts the connector library records for the object
|
|
74
|
+
* (`operation.executionType` and `operation.executionEvent`, from
|
|
75
|
+
* `registry build-library` / `registry prepare`, #1369). A value the SDK does
|
|
76
|
+
* not know is ignored, as if it were absent.
|
|
77
|
+
*/
|
|
78
|
+
export declare function libraryExecution(operation: unknown): {
|
|
79
|
+
mode?: ExternalExecutionMode;
|
|
80
|
+
event?: ExternalEventPair;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The mode and event an external task is written with: what the author passed,
|
|
84
|
+
* else what the connector library records for the object. `check` and
|
|
85
|
+
* `serialize` both call this, so they refuse the same tasks with the same text.
|
|
86
|
+
*
|
|
87
|
+
* - `mode` comes from the author or the library. With neither, the task is
|
|
88
|
+
* refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
|
|
89
|
+
* object to the sync contract without a word (#1370 was closed for that).
|
|
90
|
+
* - An authored `mode` the library contradicts is refused
|
|
91
|
+
* (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
|
|
92
|
+
* - `event` comes from the author or, for an `async` / `hybrid` object, the
|
|
93
|
+
* library; an authored one the library contradicts is refused
|
|
94
|
+
* (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
|
|
95
|
+
*
|
|
96
|
+
* @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
|
|
97
|
+
* @param authored - The task's authored `mode` and `event`.
|
|
98
|
+
* @param operation - The resolved library entry's `operation`, or `undefined`
|
|
99
|
+
* when no library is at hand (model-only `check`, which then judges only what
|
|
100
|
+
* was authored and reports no missing mode).
|
|
101
|
+
* @returns The mode and event, or the refusal.
|
|
102
|
+
*/
|
|
103
|
+
export declare function externalExecution(owner: string, authored: {
|
|
104
|
+
mode?: unknown;
|
|
105
|
+
event?: unknown;
|
|
106
|
+
}, operation: unknown): ExternalExecution | undefined;
|
|
61
107
|
/** Check's code for {@link externalDescriptorRefusal}. */
|
|
62
108
|
export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPTOR_CONTRACT";
|
|
63
109
|
/**
|
package/dist/case/external.js
CHANGED
|
@@ -45,7 +45,8 @@ export function externalEventRefusal(owner, mode, event) {
|
|
|
45
45
|
return undefined;
|
|
46
46
|
return (`${owner} is on a "hybrid" object, which answers through an event: pass the event the object reports as ` +
|
|
47
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)
|
|
48
|
+
`the "operation" and "eventMode" rows of the case spec result's caseShape.context), or prepare the connector so the ` +
|
|
49
|
+
`library records it. Without it the task has nothing to wait on.`);
|
|
49
50
|
}
|
|
50
51
|
if (mode === 'sync')
|
|
51
52
|
return `${owner} is on a "sync" object, which answers inline and has no event; drop event, or fix mode.`;
|
|
@@ -79,6 +80,83 @@ export function externalServiceType(kind, mode) {
|
|
|
79
80
|
return sync ? CASE_FORMAT_PROFILE.externalAgentSyncServiceType : CASE_FORMAT_PROFILE.externalAgentAsyncServiceType;
|
|
80
81
|
return sync ? CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType : CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType;
|
|
81
82
|
}
|
|
83
|
+
/** Check's and compile's code when an authored `mode` disagrees with the library. */
|
|
84
|
+
export const EXTERNAL_TASK_MODE_MISMATCH = 'EXTERNAL_TASK_MODE_MISMATCH';
|
|
85
|
+
/**
|
|
86
|
+
* The execution facts the connector library records for the object
|
|
87
|
+
* (`operation.executionType` and `operation.executionEvent`, from
|
|
88
|
+
* `registry build-library` / `registry prepare`, #1369). A value the SDK does
|
|
89
|
+
* not know is ignored, as if it were absent.
|
|
90
|
+
*/
|
|
91
|
+
export function libraryExecution(operation) {
|
|
92
|
+
const op = (operation ?? {});
|
|
93
|
+
const mode = isExternalExecutionMode(op.executionType) ? op.executionType : undefined;
|
|
94
|
+
const raw = op.executionEvent;
|
|
95
|
+
const event = mode !== undefined && mode !== 'sync' && typeof raw?.operation === 'string' && raw.operation
|
|
96
|
+
&& isExternalEventMode(raw.mode)
|
|
97
|
+
? { operation: raw.operation, mode: raw.mode }
|
|
98
|
+
: undefined;
|
|
99
|
+
return { ...(mode ? { mode } : {}), ...(event ? { event } : {}) };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The mode and event an external task is written with: what the author passed,
|
|
103
|
+
* else what the connector library records for the object. `check` and
|
|
104
|
+
* `serialize` both call this, so they refuse the same tasks with the same text.
|
|
105
|
+
*
|
|
106
|
+
* - `mode` comes from the author or the library. With neither, the task is
|
|
107
|
+
* refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
|
|
108
|
+
* object to the sync contract without a word (#1370 was closed for that).
|
|
109
|
+
* - An authored `mode` the library contradicts is refused
|
|
110
|
+
* (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
|
|
111
|
+
* - `event` comes from the author or, for an `async` / `hybrid` object, the
|
|
112
|
+
* library; an authored one the library contradicts is refused
|
|
113
|
+
* (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
|
|
114
|
+
*
|
|
115
|
+
* @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
|
|
116
|
+
* @param authored - The task's authored `mode` and `event`.
|
|
117
|
+
* @param operation - The resolved library entry's `operation`, or `undefined`
|
|
118
|
+
* when no library is at hand (model-only `check`, which then judges only what
|
|
119
|
+
* was authored and reports no missing mode).
|
|
120
|
+
* @returns The mode and event, or the refusal.
|
|
121
|
+
*/
|
|
122
|
+
export function externalExecution(owner, authored, operation) {
|
|
123
|
+
if (authored.mode !== undefined && !isExternalExecutionMode(authored.mode))
|
|
124
|
+
return {
|
|
125
|
+
code: 'EXTERNAL_TASK_MODE',
|
|
126
|
+
message: `${owner} mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
|
|
127
|
+
};
|
|
128
|
+
const library = operation === undefined ? {} : libraryExecution(operation);
|
|
129
|
+
if (authored.mode !== undefined && library.mode !== undefined && authored.mode !== library.mode)
|
|
130
|
+
return {
|
|
131
|
+
code: EXTERNAL_TASK_MODE_MISMATCH,
|
|
132
|
+
message: `${owner} has mode "${authored.mode}", but the connector library records the object's executionType as ` +
|
|
133
|
+
`"${library.mode}"; the plan would run the wrong contract. Drop mode to use the library's, or fix it.`,
|
|
134
|
+
};
|
|
135
|
+
const mode = authored.mode ?? library.mode;
|
|
136
|
+
if (mode === undefined) {
|
|
137
|
+
if (operation === undefined)
|
|
138
|
+
return undefined; // model-only check: the library decides
|
|
139
|
+
return {
|
|
140
|
+
code: 'EXTERNAL_TASK_MODE',
|
|
141
|
+
message: `${owner} has no mode, and the connector library records no executionType for the object. ` +
|
|
142
|
+
`Pass mode: the object's executionType from its case spec result ` +
|
|
143
|
+
`(Data.EssentialConfiguration.essentialConfiguration.executionType), or rebuild the library with registry prepare.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const authoredEvent = authored.event;
|
|
147
|
+
if (authoredEvent !== undefined && library.event !== undefined
|
|
148
|
+
&& (authoredEvent?.operation !== library.event.operation || authoredEvent?.mode !== library.event.mode))
|
|
149
|
+
return {
|
|
150
|
+
code: EXTERNAL_TASK_EVENT,
|
|
151
|
+
message: `${owner} has event ${JSON.stringify(authoredEvent)}, but the connector library records the object's event as ` +
|
|
152
|
+
`${JSON.stringify(library.event)}. Drop event to use the library's, or fix it.`,
|
|
153
|
+
};
|
|
154
|
+
const event = authoredEvent ?? (mode !== 'sync' ? library.event : undefined);
|
|
155
|
+
const refused = externalEventRefusal(owner, mode, event);
|
|
156
|
+
if (refused)
|
|
157
|
+
return { code: EXTERNAL_TASK_EVENT, message: refused };
|
|
158
|
+
return { mode, ...(event !== undefined ? { event } : {}) };
|
|
159
|
+
}
|
|
82
160
|
/** Check's code for {@link externalDescriptorRefusal}. */
|
|
83
161
|
export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTRACT';
|
|
84
162
|
/**
|