@uipath/maestro-builder-sdk 6.68.3 → 6.68.5
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/bindings-v2.d.ts +2 -9
- package/dist/bindings-v2.js +10 -20
- package/dist/case/bindings-v2.d.ts +3 -0
- package/dist/case/bindings-v2.js +281 -0
- package/dist/case/case-sdk.d.ts +33 -3
- package/dist/case/case-sdk.js +1 -0
- package/dist/case/check.js +6 -8
- package/dist/case/compile-cli.js +7 -201
- package/dist/case/decompile.js +25 -9
- package/dist/case/external.d.ts +27 -16
- package/dist/case/external.js +46 -19
- package/dist/case/serialize.js +17 -8
- package/dist/core/bindings-v2-resource.d.ts +51 -0
- package/dist/core/bindings-v2-resource.js +50 -0
- package/dist/core/refusals.d.ts +1 -1
- package/dist/core/refusals.js +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
package/dist/case/compile-cli.js
CHANGED
|
@@ -29,9 +29,9 @@ import { defaultBindingsFile } from '../workdir.js';
|
|
|
29
29
|
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
30
30
|
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
31
31
|
import { CASE_FORMAT_PROFILE } from './format-profile.js';
|
|
32
|
-
import {
|
|
32
|
+
import { deriveCaseBindingsV2 } from './bindings-v2.js';
|
|
33
|
+
import { BINDINGS_V2_DOCUMENT_VERSION } from '../core/bindings-v2-resource.js';
|
|
33
34
|
import { sha256Uuid } from '../core/stable-id.js';
|
|
34
|
-
import { isRecord } from '../core/node-envelope.js';
|
|
35
35
|
export async function run(argv) {
|
|
36
36
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
37
37
|
console.error('usage: compile <case.ts | BaseName> [-o <project>/caseplan.case]');
|
|
@@ -315,210 +315,16 @@ function syncEntryPoints(caseplan, outPath) {
|
|
|
315
315
|
* artifact. `resources refresh` reads bindings_v2.json, not caseplan.json, and
|
|
316
316
|
* succeeds with zero warnings when the sidecar is stale or empty.
|
|
317
317
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* that references both binding ids.
|
|
322
|
-
* No-op when compiling outside a scaffolded project.
|
|
318
|
+
* The entries are what `uip maestro case bindings sync` derives
|
|
319
|
+
* ({@link deriveCaseBindingsV2}). No-op when compiling outside a scaffolded
|
|
320
|
+
* project.
|
|
323
321
|
*/
|
|
324
322
|
function syncBindingsV2(caseplan, outPath) {
|
|
325
323
|
const sidecarPath = join(dirname(outPath), 'bindings_v2.json');
|
|
326
324
|
if (!existsSync(sidecarPath))
|
|
327
325
|
return -1;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
: [];
|
|
331
|
-
const byId = new Map(bindings
|
|
332
|
-
.filter((binding) => typeof binding.id === 'string')
|
|
333
|
-
.map((binding) => [binding.id, binding]));
|
|
334
|
-
const resources = [];
|
|
335
|
-
// One entry per (resource, resourceKey) group of root binding rows, in the
|
|
336
|
-
// shape `uip maestro case bindings sync` derives (`deriveCaseBindingsV2` in
|
|
337
|
-
// cli case-bindings-service.ts). `validate --strict` compares the two
|
|
338
|
-
// entry by entry as serialized JSON, so field order matters as well as
|
|
339
|
-
// field values.
|
|
340
|
-
const groups = new Map();
|
|
341
|
-
for (const binding of bindings) {
|
|
342
|
-
const resource = stringValue(binding.resource);
|
|
343
|
-
const key = stringValue(binding.resourceKey);
|
|
344
|
-
if (!resource || !key || typeof binding.propertyAttribute !== 'string')
|
|
345
|
-
continue;
|
|
346
|
-
if (resource.toLowerCase() === 'connection')
|
|
347
|
-
continue;
|
|
348
|
-
const groupKey = `${resource}\0${key}`;
|
|
349
|
-
const group = groups.get(groupKey);
|
|
350
|
-
if (group)
|
|
351
|
-
group.push(binding);
|
|
352
|
-
else
|
|
353
|
-
groups.set(groupKey, [binding]);
|
|
354
|
-
}
|
|
355
|
-
const seenConnections = new Set();
|
|
356
|
-
const connectionResources = [];
|
|
357
|
-
visitRecords(caseplan, (record) => {
|
|
358
|
-
if (!Array.isArray(record.context))
|
|
359
|
-
return;
|
|
360
|
-
const context = new Map(record.context
|
|
361
|
-
.filter(isRecord)
|
|
362
|
-
.filter((row) => typeof row.name === 'string')
|
|
363
|
-
.map((row) => [row.name, row.value]));
|
|
364
|
-
const connector = stringValue(context.get('connectorKey'));
|
|
365
|
-
const connectionId = referencedBindingValue(context.get('connection'), byId);
|
|
366
|
-
const folderKey = referencedBindingValue(context.get('folderKey'), byId);
|
|
367
|
-
if (!connector || !connectionId || !folderKey || seenConnections.has(connectionId))
|
|
368
|
-
return;
|
|
369
|
-
seenConnections.add(connectionId);
|
|
370
|
-
connectionResources.push({
|
|
371
|
-
resource: 'Connection',
|
|
372
|
-
key: connectionId,
|
|
373
|
-
value: {
|
|
374
|
-
connectionId: { defaultValue: connectionId },
|
|
375
|
-
folderKey: { defaultValue: folderKey },
|
|
376
|
-
},
|
|
377
|
-
metadata: { connector },
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
const labels = bindingLabels(caseplan.nodes);
|
|
381
|
-
for (const group of groups.values())
|
|
382
|
-
resources.push(sidecarResourceEntry(group, labels));
|
|
383
|
-
resources.push(...connectionResources);
|
|
384
|
-
writeJsonFile(sidecarPath, { version: '2.0', resources });
|
|
326
|
+
const resources = deriveCaseBindingsV2(caseplan);
|
|
327
|
+
writeJsonFile(sidecarPath, { version: BINDINGS_V2_DOCUMENT_VERSION, resources });
|
|
385
328
|
return resources.length;
|
|
386
329
|
}
|
|
387
|
-
/**
|
|
388
|
-
* The Case Designer's label for each value slot of an app or process entry.
|
|
389
|
-
* These resources get the Designer shape (cli#5003, MST-16424): an `id`, each
|
|
390
|
-
* slot flagged and labelled, and the `BindingsVersion` / `SolutionsSupport`
|
|
391
|
-
* metadata without which deploy cannot override the entry.
|
|
392
|
-
*/
|
|
393
|
-
const DESIGNER_SLOT_NAMES = new Map([
|
|
394
|
-
['app', { name: 'App name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
395
|
-
['process', { name: 'Process name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
396
|
-
['ixpDeployment', { name: 'name', folderPath: 'Folder path', solutionsSupport: false }],
|
|
397
|
-
]);
|
|
398
|
-
/** The bindings version deploy needs to override an entry. */
|
|
399
|
-
const RESOURCE_BINDINGS_VERSION = '2.2';
|
|
400
|
-
/**
|
|
401
|
-
* One sidecar entry for a group of root binding rows that share a resource and
|
|
402
|
-
* a resource key. The subtype comes from the rows only: a Flow's rows carry
|
|
403
|
-
* none, so its entry has none either, as the Designer saves it.
|
|
404
|
-
*
|
|
405
|
-
* `metadata.ActivityName` labels a Designer-shaped entry with the first task
|
|
406
|
-
* that references one of its rows ({@link bindingLabels}).
|
|
407
|
-
*/
|
|
408
|
-
function sidecarResourceEntry(group, labels) {
|
|
409
|
-
const resource = group[0].resource;
|
|
410
|
-
const key = group[0].resourceKey;
|
|
411
|
-
const attributeDefault = (attribute) => {
|
|
412
|
-
const found = group.find((binding) => binding.propertyAttribute === attribute)?.default;
|
|
413
|
-
return typeof found === 'string' ? found : undefined;
|
|
414
|
-
};
|
|
415
|
-
// An in-solution resource binds an empty folderPath in the caseplan, while
|
|
416
|
-
// the sidecar names its folder by the sentinel the key carries.
|
|
417
|
-
const folderPath = isInSolutionKey(key) ? SOLUTION_FOLDER : attributeDefault('folderPath');
|
|
418
|
-
const name = attributeDefault('name');
|
|
419
|
-
const subType = group.map((binding) => binding.resourceSubType).find((found) => found !== undefined);
|
|
420
|
-
const designer = DESIGNER_SLOT_NAMES.get(resource);
|
|
421
|
-
if (designer) {
|
|
422
|
-
const value = {};
|
|
423
|
-
if (folderPath !== undefined) {
|
|
424
|
-
value.folderPath = { defaultValue: folderPath, isExpression: false, displayName: designer.folderPath };
|
|
425
|
-
}
|
|
426
|
-
if (name !== undefined) {
|
|
427
|
-
value.name = { defaultValue: name, isExpression: false, displayName: designer.name };
|
|
428
|
-
}
|
|
429
|
-
const activityName = group
|
|
430
|
-
.map((binding) => (typeof binding.id === 'string' ? labels.get(binding.id) : undefined))
|
|
431
|
-
.find((found) => found !== undefined);
|
|
432
|
-
return {
|
|
433
|
-
resource,
|
|
434
|
-
key,
|
|
435
|
-
id: `${resource}${key}`,
|
|
436
|
-
value,
|
|
437
|
-
metadata: {
|
|
438
|
-
...(activityName !== undefined && { ActivityName: activityName }),
|
|
439
|
-
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
440
|
-
DisplayLabel: 'name',
|
|
441
|
-
...(designer.solutionsSupport && { SolutionsSupport: 'true' }),
|
|
442
|
-
...(subType !== undefined && { subType }),
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
const value = {};
|
|
447
|
-
if (name !== undefined)
|
|
448
|
-
value.name = { defaultValue: name };
|
|
449
|
-
if (folderPath !== undefined)
|
|
450
|
-
value.folderPath = { defaultValue: folderPath };
|
|
451
|
-
// A solution-resource picker (such as a Data Fabric entity) carries its folder key as a slot of its own.
|
|
452
|
-
const folderKey = attributeDefault('folderKey');
|
|
453
|
-
if (folderKey !== undefined)
|
|
454
|
-
value.folderKey = { defaultValue: folderKey };
|
|
455
|
-
return {
|
|
456
|
-
resource,
|
|
457
|
-
key,
|
|
458
|
-
value,
|
|
459
|
-
metadata: {
|
|
460
|
-
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
461
|
-
...(subType !== undefined && { subType }),
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* The label of the first object that references each binding id
|
|
467
|
-
* (`=bindings.<id>`), in document order: the nearest enclosing `displayName`,
|
|
468
|
-
* `display.label` or `label`. The same walk as the CLI's
|
|
469
|
-
* `collectBindingLabels`, so both name an entry after the same task.
|
|
470
|
-
*/
|
|
471
|
-
function bindingLabels(nodes) {
|
|
472
|
-
const found = new Map();
|
|
473
|
-
const ownLabel = (node) => {
|
|
474
|
-
const display = isRecord(node.display) ? node.display.label : undefined;
|
|
475
|
-
return [node.displayName, display, node.label].map(stringValue).find((label) => label !== undefined);
|
|
476
|
-
};
|
|
477
|
-
const walk = (node, label) => {
|
|
478
|
-
if (typeof node === 'string') {
|
|
479
|
-
if (label !== undefined && node.startsWith('=bindings.')) {
|
|
480
|
-
const id = node.slice('=bindings.'.length);
|
|
481
|
-
if (!found.has(id))
|
|
482
|
-
found.set(id, label);
|
|
483
|
-
}
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
if (Array.isArray(node)) {
|
|
487
|
-
for (const item of node)
|
|
488
|
-
walk(item, label);
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
if (!isRecord(node))
|
|
492
|
-
return;
|
|
493
|
-
const scoped = ownLabel(node) ?? label;
|
|
494
|
-
for (const value of Object.values(node))
|
|
495
|
-
walk(value, scoped);
|
|
496
|
-
};
|
|
497
|
-
walk(nodes ?? [], undefined);
|
|
498
|
-
return found;
|
|
499
|
-
}
|
|
500
|
-
function referencedBindingValue(value, bindings) {
|
|
501
|
-
const ref = stringValue(value);
|
|
502
|
-
if (!ref)
|
|
503
|
-
return undefined;
|
|
504
|
-
if (!ref.startsWith('=bindings.'))
|
|
505
|
-
return ref;
|
|
506
|
-
const binding = bindings.get(ref.slice('=bindings.'.length));
|
|
507
|
-
return binding && (stringValue(binding.default) ?? stringValue(binding.resourceKey));
|
|
508
|
-
}
|
|
509
|
-
function visitRecords(value, visit) {
|
|
510
|
-
if (Array.isArray(value)) {
|
|
511
|
-
for (const item of value)
|
|
512
|
-
visitRecords(item, visit);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
if (!isRecord(value))
|
|
516
|
-
return;
|
|
517
|
-
visit(value);
|
|
518
|
-
for (const child of Object.values(value))
|
|
519
|
-
visitRecords(child, visit);
|
|
520
|
-
}
|
|
521
|
-
function stringValue(value) {
|
|
522
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
523
|
-
}
|
|
524
330
|
runWhenInvokedDirectly(import.meta.url, 'compile', run);
|
package/dist/case/decompile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { commentText } from '../core/comment-text.js';
|
|
2
2
|
import { tsLiteral, tsNumber } from '../core/ts-literal.js';
|
|
3
3
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
4
|
-
import { EXTERNAL_TASK_CONTEXT, externalDescriptorRefusal,
|
|
4
|
+
import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
|
|
5
5
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
6
6
|
import { ownEntry } from '../core/own-entry.js';
|
|
7
7
|
import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
|
|
@@ -618,7 +618,9 @@ function connectorCall(task, refs, library) {
|
|
|
618
618
|
/**
|
|
619
619
|
* Lift an External Agent / External Workflow task saved in the Case Designer's
|
|
620
620
|
* shape (the one `serialize` emits): a connector activity's context and input
|
|
621
|
-
* containers plus the object's `executionType`, which becomes `mode
|
|
621
|
+
* containers plus the object's `executionType`, which becomes `mode`, and the
|
|
622
|
+
* event pair an async / hybrid object answers with (`operation` and `eventMode`
|
|
623
|
+
* rows), which becomes `event`. The
|
|
622
624
|
* service type must be the one that execution type gives, so a plan whose two
|
|
623
625
|
* disagree is refused rather than lifted to a source that would compile to
|
|
624
626
|
* something else.
|
|
@@ -637,19 +639,20 @@ function externalCall(task, refs, library) {
|
|
|
637
639
|
throw new Error(`${owner} data.bindings must be an empty array; bindings live at case root.`);
|
|
638
640
|
if (!Array.isArray(data.context))
|
|
639
641
|
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.
|
|
642
|
+
// Exactly the context rows serialize writes (the Designer's set, plus the
|
|
643
|
+
// event pair an async / hybrid object answers with): an unknown or repeated
|
|
644
|
+
// row would be dropped by the lift and lost on recompile.
|
|
646
645
|
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);
|
|
646
|
+
const unknownContext = contextNames.find((name, at) => !(EXTERNAL_TASK_CONTEXT.includes(name) || EXTERNAL_TASK_EVENT_CONTEXT.includes(name)) || contextNames.indexOf(name) !== at);
|
|
648
647
|
if (unknownContext !== undefined)
|
|
649
648
|
throw new Error(`${owner} has unsupported or repeated context row "${unknownContext}".`);
|
|
650
649
|
const absentContext = EXTERNAL_TASK_CONTEXT.find((name) => !contextNames.includes(name));
|
|
651
650
|
if (absentContext)
|
|
652
651
|
throw new Error(`${owner} is missing context row "${absentContext}".`);
|
|
652
|
+
// The pair is written whole or not at all, so half of one cannot be lifted.
|
|
653
|
+
const eventRows = EXTERNAL_TASK_EVENT_CONTEXT.filter((name) => contextNames.includes(name));
|
|
654
|
+
if (eventRows.length === 1)
|
|
655
|
+
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
656
|
const context = contextMap(data);
|
|
654
657
|
const stringValue = (name) => {
|
|
655
658
|
const row = context.get(name);
|
|
@@ -672,6 +675,18 @@ function externalCall(task, refs, library) {
|
|
|
672
675
|
const serviceType = externalServiceType(kind, mode);
|
|
673
676
|
if (data.serviceType !== serviceType)
|
|
674
677
|
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: the same rule compile applies,
|
|
679
|
+
// so a lifted task always compiles back (a hybrid task without one does not).
|
|
680
|
+
const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
|
|
681
|
+
// A saved plan has no `event` option to drop, so a sync task with event rows
|
|
682
|
+
// gets its own text naming the rows; every other case is compile's rule.
|
|
683
|
+
if (mode === 'sync' && event)
|
|
684
|
+
throw new Error(`${owner} has context rows "operation" (${JSON.stringify(event.operation)}) and "eventMode" ` +
|
|
685
|
+
`(${JSON.stringify(event.mode)}) on executionType "sync"; a sync object answers inline and has no event. ` +
|
|
686
|
+
`Remove the two rows, or fix executionType and serviceType if the object is async or hybrid.`);
|
|
687
|
+
const eventRefused = externalEventRefusal(owner, mode, event);
|
|
688
|
+
if (eventRefused)
|
|
689
|
+
throw new Error(eventRefused);
|
|
675
690
|
const identity = library.identifyAction({
|
|
676
691
|
connectorKey: stringValue('connectorKey'),
|
|
677
692
|
httpMethod: stringValue('method'),
|
|
@@ -691,6 +706,7 @@ function externalCall(task, refs, library) {
|
|
|
691
706
|
['connection', tsLiteral(binding('connection'))],
|
|
692
707
|
['folder', tsLiteral(binding('folderKey'))],
|
|
693
708
|
['mode', tsLiteral(mode)],
|
|
709
|
+
['event', event ? opts([['operation', tsLiteral(event.operation)], ['mode', tsLiteral(event.mode)]]) : undefined],
|
|
694
710
|
['inputs', tsLiteral(inputs)],
|
|
695
711
|
]);
|
|
696
712
|
const method = kind === 'external-agent' ? 'externalAgent' : 'externalWorkflow';
|
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) {
|
package/dist/case/serialize.js
CHANGED
|
@@ -19,7 +19,7 @@ import { bindingLabelRefusal } from '../core/binding-messages.js';
|
|
|
19
19
|
import { resolveEventWhere } from '../core/lookups.js';
|
|
20
20
|
import { resolveCaseSubscription } from './subscription.js';
|
|
21
21
|
import { dedupeConditionNames } from './rule-names.js';
|
|
22
|
-
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT,
|
|
22
|
+
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_EVENT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode, } from './external.js';
|
|
23
23
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, caseVersionMismatch } from './format-profile.js';
|
|
24
24
|
import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './resource-identity.js';
|
|
25
25
|
import { ownEntry } from '../core/own-entry.js';
|
|
@@ -1739,7 +1739,7 @@ function businessRuleTaskData(task, resolver, elementId, ids) {
|
|
|
1739
1739
|
* the op's output schema. Connection/folder resolve through `bindings` and are
|
|
1740
1740
|
* referenced as `=bindings.<id>`, with the binding entry collected at case root.
|
|
1741
1741
|
*/
|
|
1742
|
-
function connectorTaskData(spec, resolver, elementId, where, executionType) {
|
|
1742
|
+
function connectorTaskData(spec, resolver, elementId, where, executionType, event) {
|
|
1743
1743
|
if (!resolver.library)
|
|
1744
1744
|
throw new CompileRefusal('LIBRARY_REQUIRED', `connector task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
|
|
1745
1745
|
const rc = resolver.library.resolve(spec.key, spec.action, spec.version, spec.object);
|
|
@@ -1787,7 +1787,7 @@ function connectorTaskData(spec, resolver, elementId, where, executionType) {
|
|
|
1787
1787
|
// from the `body` slot. This used to drop the whole wire and push a filled
|
|
1788
1788
|
// file part into the body, where the runtime does not look for it (#1420).
|
|
1789
1789
|
const fileParts = (slots.multipartParameters ?? []).filter((part) => part.dataType.toLowerCase() === 'file');
|
|
1790
|
-
context.push(io('metadata', 'json', { body: connectorMetadata(rc, op, slots, elementId, executionType) }));
|
|
1790
|
+
context.push(io('metadata', 'json', { body: connectorMetadata(rc, op, slots, elementId, executionType, event) }));
|
|
1791
1791
|
const inputs = [
|
|
1792
1792
|
ioSlot('pathParameters', pathParameters, `${elementId}`),
|
|
1793
1793
|
ioSlot('queryParameters', queryParameters, `${elementId}`),
|
|
@@ -1816,14 +1816,20 @@ function externalTaskData(kind, spec, resolver, elementId, where) {
|
|
|
1816
1816
|
throw new CompileRefusal(EXTERNAL_TASK_DESCRIPTOR_CONTRACT, refused);
|
|
1817
1817
|
if (!isExternalExecutionMode(spec.mode))
|
|
1818
1818
|
throw new CompileRefusal('EXTERNAL_TASK_MODE', `${kind} task mode must be the connector object's executionType: "sync", "async" or "hybrid".`);
|
|
1819
|
-
|
|
1820
|
-
|
|
1819
|
+
const eventRefused = externalEventRefusal(where, spec.mode, spec.event);
|
|
1820
|
+
if (eventRefused)
|
|
1821
|
+
throw new CompileRefusal(EXTERNAL_TASK_EVENT, eventRefused);
|
|
1821
1822
|
if (!spec.connector.connection || !spec.connector.folder)
|
|
1822
1823
|
throw new CompileRefusal(spec.connector.connection ? 'EXTERNAL_TASK_FOLDER' : 'EXTERNAL_TASK_CONNECTION', `${kind} task requires non-empty connection and folder binding names.`);
|
|
1823
|
-
const data = connectorTaskData(spec.connector, resolver, elementId, where, spec.mode);
|
|
1824
|
+
const data = connectorTaskData(spec.connector, resolver, elementId, where, spec.mode, spec.event);
|
|
1824
1825
|
const context = data.context;
|
|
1825
1826
|
const metadataAt = context.findIndex((row) => row.name === 'metadata');
|
|
1826
|
-
|
|
1827
|
+
// The Designer's order: the event pair (when the object answers through one),
|
|
1828
|
+
// then the execution type, just before `metadata`.
|
|
1829
|
+
context.splice(metadataAt, 0, ...(spec.event ? [
|
|
1830
|
+
io('operation', 'string', { value: spec.event.operation }),
|
|
1831
|
+
io('eventMode', 'string', { value: spec.event.mode }),
|
|
1832
|
+
] : []), io('executionType', 'string', { value: spec.mode }));
|
|
1827
1833
|
context.push(io('activityConfigurationVersion', 'string', { value: 'v2' }));
|
|
1828
1834
|
return { ...data, serviceType: externalServiceType(kind, spec.mode) };
|
|
1829
1835
|
}
|
|
@@ -1957,7 +1963,7 @@ function folderBindingRef(resolver, name, connection, site) {
|
|
|
1957
1963
|
* Generic activity, and the library version where the platform writes
|
|
1958
1964
|
* `packageVersion: "1.0.0"` (#1420).
|
|
1959
1965
|
*/
|
|
1960
|
-
function connectorMetadata(rc, op, slots, seed, executionType) {
|
|
1966
|
+
function connectorMetadata(rc, op, slots, seed, executionType, event) {
|
|
1961
1967
|
// The part DECLARATIONS only: a file part's value rides on its `file` input.
|
|
1962
1968
|
const multipartParameters = (slots.multipartParameters ?? []).map(({ name, dataType }) => ({ name, dataType }));
|
|
1963
1969
|
// The configuration replays the authored values (a schema action's parent
|
|
@@ -1973,6 +1979,9 @@ function connectorMetadata(rc, op, slots, seed, executionType) {
|
|
|
1973
1979
|
uiPathActivityTypeId: rc.activityTypeId ?? '',
|
|
1974
1980
|
errorState: { issues: [] },
|
|
1975
1981
|
...(executionType ? { executionType } : {}),
|
|
1982
|
+
// The event an async / hybrid object answers with, named as the Designer
|
|
1983
|
+
// names it here (`eventType`, where the context row is `operation`).
|
|
1984
|
+
...(event ? { eventType: event.operation, eventMode: event.mode } : {}),
|
|
1976
1985
|
},
|
|
1977
1986
|
activityMetadata: {
|
|
1978
1987
|
activity: {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `bindings_v2.json` resource row, and the connection row every artifact
|
|
3
|
+
* writes.
|
|
4
|
+
*
|
|
5
|
+
* Flow (`bindings-v2.ts`) and Case (`case/bindings-v2.ts`) both declare a
|
|
6
|
+
* connector's connection in the project sidecar, and the CLI checks each
|
|
7
|
+
* against its own generator: `uip maestro flow pack` for Flow, `uip maestro
|
|
8
|
+
* case bindings sync` (`connectionEntry` in cli
|
|
9
|
+
* `packages/case-tool/src/services/case-bindings-service.ts`) for Case. Both
|
|
10
|
+
* generators write the same row — the Designer shape, `id` + flagged
|
|
11
|
+
* `ConnectionId` slot + `BindingsVersion` / `UseConnectionService` metadata.
|
|
12
|
+
* The CLI compares rows as serialized JSON, so the key order below is part of
|
|
13
|
+
* the contract.
|
|
14
|
+
*
|
|
15
|
+
* Case used to write its own older row (`connectionId` slot, a lower-case
|
|
16
|
+
* `connector` metadata key) and `validate --strict` reported it stale
|
|
17
|
+
* (builder-sdk#1382). One builder for both artifacts is what keeps the two
|
|
18
|
+
* from drifting apart again.
|
|
19
|
+
*/
|
|
20
|
+
/** One declared solution resource. */
|
|
21
|
+
export interface BindingsV2Resource {
|
|
22
|
+
resource: string;
|
|
23
|
+
key: string;
|
|
24
|
+
id?: string;
|
|
25
|
+
value: Record<string, unknown>;
|
|
26
|
+
metadata: Record<string, unknown>;
|
|
27
|
+
[k: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** The `bindings_v2.json` document version every generator writes. */
|
|
30
|
+
export declare const BINDINGS_V2_DOCUMENT_VERSION = "2.0";
|
|
31
|
+
/** The per-resource `metadata.BindingsVersion` deploy needs to override an entry. */
|
|
32
|
+
export declare const RESOURCE_BINDINGS_VERSION = "2.2";
|
|
33
|
+
/** What one connection row is made of. Optional parts are left out of the row when absent. */
|
|
34
|
+
export interface ConnectionResourceParts {
|
|
35
|
+
/** `connection` in a `.flow`, `Connection` in a caseplan: each artifact keeps its own spelling. */
|
|
36
|
+
resource: string;
|
|
37
|
+
/** The connection's resource key. */
|
|
38
|
+
key: string;
|
|
39
|
+
/** The `ConnectionId` slot's value; the key when the binding row has no default. */
|
|
40
|
+
connectionId: string;
|
|
41
|
+
/** The connection binding's name: the slot's `displayName` and `metadata.DisplayLabel`. */
|
|
42
|
+
displayLabel?: string;
|
|
43
|
+
/** The folder key slot. Case writes it; Flow's pack does not. */
|
|
44
|
+
folderKey?: string;
|
|
45
|
+
/** The label of the first step or task that uses the connection. */
|
|
46
|
+
activityName?: string;
|
|
47
|
+
/** The connector the connection belongs to. */
|
|
48
|
+
connector?: string;
|
|
49
|
+
}
|
|
50
|
+
/** The connection row, in the Designer shape both CLI generators derive. */
|
|
51
|
+
export declare function connectionResource(parts: ConnectionResourceParts): BindingsV2Resource;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `bindings_v2.json` resource row, and the connection row every artifact
|
|
3
|
+
* writes.
|
|
4
|
+
*
|
|
5
|
+
* Flow (`bindings-v2.ts`) and Case (`case/bindings-v2.ts`) both declare a
|
|
6
|
+
* connector's connection in the project sidecar, and the CLI checks each
|
|
7
|
+
* against its own generator: `uip maestro flow pack` for Flow, `uip maestro
|
|
8
|
+
* case bindings sync` (`connectionEntry` in cli
|
|
9
|
+
* `packages/case-tool/src/services/case-bindings-service.ts`) for Case. Both
|
|
10
|
+
* generators write the same row — the Designer shape, `id` + flagged
|
|
11
|
+
* `ConnectionId` slot + `BindingsVersion` / `UseConnectionService` metadata.
|
|
12
|
+
* The CLI compares rows as serialized JSON, so the key order below is part of
|
|
13
|
+
* the contract.
|
|
14
|
+
*
|
|
15
|
+
* Case used to write its own older row (`connectionId` slot, a lower-case
|
|
16
|
+
* `connector` metadata key) and `validate --strict` reported it stale
|
|
17
|
+
* (builder-sdk#1382). One builder for both artifacts is what keeps the two
|
|
18
|
+
* from drifting apart again.
|
|
19
|
+
*/
|
|
20
|
+
/** The `bindings_v2.json` document version every generator writes. */
|
|
21
|
+
export const BINDINGS_V2_DOCUMENT_VERSION = '2.0';
|
|
22
|
+
/** The per-resource `metadata.BindingsVersion` deploy needs to override an entry. */
|
|
23
|
+
export const RESOURCE_BINDINGS_VERSION = '2.2';
|
|
24
|
+
/** The connection row, in the Designer shape both CLI generators derive. */
|
|
25
|
+
export function connectionResource(parts) {
|
|
26
|
+
const { resource, key, connectionId, displayLabel, folderKey, activityName, connector } = parts;
|
|
27
|
+
const value = {
|
|
28
|
+
ConnectionId: {
|
|
29
|
+
defaultValue: connectionId,
|
|
30
|
+
isExpression: false,
|
|
31
|
+
...(displayLabel !== undefined && { displayName: displayLabel }),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
if (folderKey !== undefined)
|
|
35
|
+
value.folderKey = { defaultValue: folderKey };
|
|
36
|
+
return {
|
|
37
|
+
resource,
|
|
38
|
+
key,
|
|
39
|
+
// The generators concatenate resource and key with no separator.
|
|
40
|
+
id: `${resource}${key}`,
|
|
41
|
+
value,
|
|
42
|
+
metadata: {
|
|
43
|
+
...(activityName !== undefined && { ActivityName: activityName }),
|
|
44
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
45
|
+
...(displayLabel !== undefined && { DisplayLabel: displayLabel }),
|
|
46
|
+
UseConnectionService: 'true',
|
|
47
|
+
...(connector !== undefined && { Connector: connector }),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
package/dist/core/refusals.d.ts
CHANGED
|
@@ -274,8 +274,8 @@ export declare const REFUSALS: {
|
|
|
274
274
|
readonly EXTERNAL_TASK_DESCRIPTOR: "lint";
|
|
275
275
|
readonly EXTERNAL_TASK_DESCRIPTOR_CONTRACT: "lowering";
|
|
276
276
|
readonly EXTERNAL_TASK_EMPTY_DATA: "lint";
|
|
277
|
+
readonly EXTERNAL_TASK_EVENT: "lowering";
|
|
277
278
|
readonly EXTERNAL_TASK_FOLDER: "lowering";
|
|
278
|
-
readonly EXTERNAL_TASK_HYBRID_UNSUPPORTED: "lowering";
|
|
279
279
|
readonly EXTERNAL_TASK_MODE: "lowering";
|
|
280
280
|
readonly EXTRACT_SCHEMA_SHAPE: "lint";
|
|
281
281
|
readonly FAKE_JOIN: "lint";
|
package/dist/core/refusals.js
CHANGED
|
@@ -325,8 +325,8 @@ export const REFUSALS = {
|
|
|
325
325
|
EXTERNAL_TASK_DESCRIPTOR: 'lint',
|
|
326
326
|
EXTERNAL_TASK_DESCRIPTOR_CONTRACT: 'lowering',
|
|
327
327
|
EXTERNAL_TASK_EMPTY_DATA: 'lint',
|
|
328
|
+
EXTERNAL_TASK_EVENT: 'lowering',
|
|
328
329
|
EXTERNAL_TASK_FOLDER: 'lowering',
|
|
329
|
-
EXTERNAL_TASK_HYBRID_UNSUPPORTED: 'lowering',
|
|
330
330
|
EXTERNAL_TASK_MODE: 'lowering',
|
|
331
331
|
EXTRACT_SCHEMA_SHAPE: 'lint',
|
|
332
332
|
FAKE_JOIN: 'lint',
|