@uipath/maestro-builder-sdk 6.20.6 → 6.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +72 -72
- package/dist/api-members.md +143 -143
- package/dist/check.js +19 -157
- package/dist/core/actions.d.ts +0 -126
- package/dist/core/actions.js +0 -48
- package/dist/core/edit-distance.d.ts +8 -0
- package/dist/core/edit-distance.js +26 -0
- package/dist/core/nodes/node-type.d.ts +17 -2
- package/dist/core/typed-raw-node.js +0 -2
- package/dist/decompile.js +0 -31
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +6 -0
- package/dist/flow-sdk.d.ts +1 -0
- package/dist/flow-sdk.js +1 -0
- package/dist/nodes/queue/flow/check.d.ts +35 -0
- package/dist/nodes/queue/flow/check.js +126 -0
- package/dist/nodes/queue/flow/factories.d.ts +49 -0
- package/dist/nodes/queue/flow/factories.js +50 -0
- package/dist/nodes/queue/flow/family.d.ts +3 -0
- package/dist/nodes/queue/flow/family.js +8 -0
- package/dist/nodes/queue/flow/index.d.ts +3 -0
- package/dist/nodes/queue/flow/index.js +3 -0
- package/dist/nodes/queue/flow/node-type.d.ts +8 -0
- package/dist/nodes/queue/flow/node-type.js +265 -0
- package/dist/nodes/queue/flow/spec.d.ts +92 -0
- package/dist/nodes/queue/flow/spec.js +1 -0
- package/dist/serialize.js +1 -161
- package/package.json +2 -2
- /package/dist/{core/queue-record.d.ts → nodes/queue/flow/record.d.ts} +0 -0
- /package/dist/{core/queue-record.js → nodes/queue/flow/record.js} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Queue family's rules and their catalog. Moved from check.ts in Phase 4 of
|
|
3
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; codes and messages are unchanged.
|
|
4
|
+
*/
|
|
5
|
+
import type { DiagnosticCatalog } from '../../../core/nodes/family.js';
|
|
6
|
+
import type { FlowCheckContext } from '../../../flow-node-type.js';
|
|
7
|
+
import type { QueueItemInputs } from './spec.js';
|
|
8
|
+
/** Every code the family reports, with the severity it reports it at. */
|
|
9
|
+
export declare const QUEUE_DIAGNOSTICS: DiagnosticCatalog;
|
|
10
|
+
/**
|
|
11
|
+
* Check a `queueItem(...)` call.
|
|
12
|
+
*
|
|
13
|
+
* WHAT THESE RULES CAN AND CANNOT SEE. Whether the queue exists, whether anything
|
|
14
|
+
* drains it, and what a consumer writes into a processed item's result all live on
|
|
15
|
+
* the tenant. So these cover the mistakes decidable from the call — plus the one
|
|
16
|
+
* mistake the PLATFORM decides and rejects at run time, which is worth catching
|
|
17
|
+
* here because its error arrives from Orchestrator as an HTTP 400 three layers
|
|
18
|
+
* away:
|
|
19
|
+
*
|
|
20
|
+
* - a missing/blank `queue`, `folderPath` or `key`. Without the first two
|
|
21
|
+
* nothing can resolve the node's `resourceBindings.name`, and without `key`
|
|
22
|
+
* nothing joins the node to its bindings — both read as engine errors rather
|
|
23
|
+
* than authoring ones.
|
|
24
|
+
* - a `key` that is not a GUID. The commonest form is passing the queue's NAME
|
|
25
|
+
* where its Orchestrator key belongs.
|
|
26
|
+
* - a NESTED `item`. Orchestrator answers "Property values must be simple (like
|
|
27
|
+
* numbers or text) and not complex or composite data types (like objects or
|
|
28
|
+
* arrays)" — a run-time 400 for something visible in the source.
|
|
29
|
+
* - a `priority` outside the three the node type offers. The platform normalizes
|
|
30
|
+
* an unknown one silently, so it is a wrong RESULT, not an error.
|
|
31
|
+
* - `returns` on a step that does not wait (there is nothing per-tenant to
|
|
32
|
+
* declare), and a `wait: true` step whose result nothing can read.
|
|
33
|
+
* - a non-ISO `dueDate` / `deferDate`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkQueueItem(inputs: QueueItemInputs | undefined, ctx: FlowCheckContext): void;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { commentText } from '../../../core/comment-text.js';
|
|
2
|
+
import { nearestName } from '../../../core/edit-distance.js';
|
|
3
|
+
import { Expr } from '../../../core/expr.js';
|
|
4
|
+
import { GUID_RE } from '../../../core/binding-messages.js';
|
|
5
|
+
/** Every code the family reports, with the severity it reports it at. */
|
|
6
|
+
export const QUEUE_DIAGNOSTICS = {
|
|
7
|
+
QUEUE_NO_NAME: { severity: 'error', summary: 'no queue name' },
|
|
8
|
+
QUEUE_NO_FOLDER: { severity: 'error', summary: 'no folderPath' },
|
|
9
|
+
QUEUE_NO_KEY: { severity: 'error', summary: 'no queue key' },
|
|
10
|
+
QUEUE_KEY_NOT_GUID: { severity: 'error', summary: 'the key is not an Orchestrator GUID' },
|
|
11
|
+
QUEUE_NO_ITEM: { severity: 'warning', summary: 'the item carries no data' },
|
|
12
|
+
QUEUE_ITEM_NOT_OBJECT: { severity: 'error', summary: 'the item is not an object' },
|
|
13
|
+
QUEUE_ITEM_FIELD_NOT_PRIMITIVE: { severity: 'error', summary: 'an item field is not a primitive' },
|
|
14
|
+
QUEUE_BAD_PRIORITY: { severity: 'error', summary: 'a priority the node type does not offer' },
|
|
15
|
+
QUEUE_BAD_DATE: { severity: 'error', summary: 'a date Orchestrator cannot read' },
|
|
16
|
+
QUEUE_RETURNS_WITHOUT_WAIT: { severity: 'warning', summary: 'returns without wait: true' },
|
|
17
|
+
QUEUE_READ_NOT_OUTPUT: { severity: 'error', summary: 'a read outside the step\'s output' },
|
|
18
|
+
QUEUE_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'a field read off a wait result with no returns' },
|
|
19
|
+
QUEUE_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'a read of a field the step does not publish' },
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Check a `queueItem(...)` call.
|
|
23
|
+
*
|
|
24
|
+
* WHAT THESE RULES CAN AND CANNOT SEE. Whether the queue exists, whether anything
|
|
25
|
+
* drains it, and what a consumer writes into a processed item's result all live on
|
|
26
|
+
* the tenant. So these cover the mistakes decidable from the call — plus the one
|
|
27
|
+
* mistake the PLATFORM decides and rejects at run time, which is worth catching
|
|
28
|
+
* here because its error arrives from Orchestrator as an HTTP 400 three layers
|
|
29
|
+
* away:
|
|
30
|
+
*
|
|
31
|
+
* - a missing/blank `queue`, `folderPath` or `key`. Without the first two
|
|
32
|
+
* nothing can resolve the node's `resourceBindings.name`, and without `key`
|
|
33
|
+
* nothing joins the node to its bindings — both read as engine errors rather
|
|
34
|
+
* than authoring ones.
|
|
35
|
+
* - a `key` that is not a GUID. The commonest form is passing the queue's NAME
|
|
36
|
+
* where its Orchestrator key belongs.
|
|
37
|
+
* - a NESTED `item`. Orchestrator answers "Property values must be simple (like
|
|
38
|
+
* numbers or text) and not complex or composite data types (like objects or
|
|
39
|
+
* arrays)" — a run-time 400 for something visible in the source.
|
|
40
|
+
* - a `priority` outside the three the node type offers. The platform normalizes
|
|
41
|
+
* an unknown one silently, so it is a wrong RESULT, not an error.
|
|
42
|
+
* - `returns` on a step that does not wait (there is nothing per-tenant to
|
|
43
|
+
* declare), and a `wait: true` step whose result nothing can read.
|
|
44
|
+
* - a non-ISO `dueDate` / `deferDate`.
|
|
45
|
+
*/
|
|
46
|
+
export function checkQueueItem(inputs, ctx) {
|
|
47
|
+
const step = ctx.step;
|
|
48
|
+
// Severity is the catalog's (QUEUE_DIAGNOSTICS): `at` and `warn` name the intent.
|
|
49
|
+
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
50
|
+
const warn = at;
|
|
51
|
+
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
52
|
+
if (blank(inputs?.queue)) {
|
|
53
|
+
at('QUEUE_NO_NAME', `Queue step "${step}" has no \`queue\`. That is the queue's name in Orchestrator, and the `
|
|
54
|
+
+ `runtime resolves the queue by name + folder — without it the run fails before dispatch.`, `queue: 'SupplierInvoices'`);
|
|
55
|
+
}
|
|
56
|
+
if (blank(inputs?.folderPath)) {
|
|
57
|
+
at('QUEUE_NO_FOLDER', `Queue step "${step}" has no \`folderPath\`. Queues are folder-scoped, and the runtime `
|
|
58
|
+
+ `resolves this one by name + folder.`, `folderPath: 'Shared'`);
|
|
59
|
+
}
|
|
60
|
+
if (blank(inputs?.key)) {
|
|
61
|
+
at('QUEUE_NO_KEY', `Queue step "${step}" has no \`key\`. That is the queue's own Orchestrator key, and it is `
|
|
62
|
+
+ `what joins this node to the flow's queue bindings — without it nothing says which `
|
|
63
|
+
+ `bindings belong to the node, so it cannot be dispatched. Find it with `
|
|
64
|
+
+ `\`uip or queues list --all-folders\`.`, `key: 'fa1a7328-cd17-4525-b5ab-29d86e399312'`);
|
|
65
|
+
}
|
|
66
|
+
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
67
|
+
at('QUEUE_KEY_NOT_GUID', `Queue step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not an `
|
|
68
|
+
+ `Orchestrator key. \`key\` is the GUID the tenant gives the queue; the queue's NAME goes `
|
|
69
|
+
+ `in \`queue\` and its folder in \`folderPath\`.`, `key: '<guid>', queue: ${JSON.stringify(inputs.key)}`);
|
|
70
|
+
}
|
|
71
|
+
// The item's data must be FLAT. This is the platform's rule, not ours, and it is
|
|
72
|
+
// enforced at the far end: a nested value earns an HTTP 400 from Orchestrator
|
|
73
|
+
// mid-run, which is a long way from the line that caused it.
|
|
74
|
+
const item = inputs?.item;
|
|
75
|
+
if (item === undefined || item === null) {
|
|
76
|
+
warn('QUEUE_NO_ITEM', `Queue step "${step}" has no \`item\`, so it enqueues an item with no data. That is legal `
|
|
77
|
+
+ `— a bare item with a reference is a real pattern — but if the work needs a payload, this `
|
|
78
|
+
+ `is where it goes.`, `item: { InvoiceId: input('invoiceRef') }`);
|
|
79
|
+
}
|
|
80
|
+
else if (typeof item !== 'object' || Array.isArray(item)) {
|
|
81
|
+
at('QUEUE_ITEM_NOT_OBJECT', `Queue step "${step}"'s \`item\` is ${Array.isArray(item) ? 'an array' : typeof item}. It `
|
|
82
|
+
+ `must be an object of field names to primitive values — that object becomes the queue `
|
|
83
|
+
+ `item's \`SpecificContent\`.`, `item: { InvoiceId: 'INV-1', Amount: 120 }`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
for (const [k, v] of Object.entries(item)) {
|
|
87
|
+
// An Expr is a reference the runtime resolves — its VALUE may be anything,
|
|
88
|
+
// and nothing local can know, so it is not flagged here.
|
|
89
|
+
if (v instanceof Expr)
|
|
90
|
+
continue;
|
|
91
|
+
if (v !== null && typeof v === 'object') {
|
|
92
|
+
at('QUEUE_ITEM_FIELD_NOT_PRIMITIVE', `Queue step "${step}"'s \`item.${k}\` is ${Array.isArray(v) ? 'an array' : 'an object'}. `
|
|
93
|
+
+ `Orchestrator rejects a queue item whose property values are not simple: `
|
|
94
|
+
+ `"Property values must be simple (like numbers or text) and not complex or composite `
|
|
95
|
+
+ `data types (like objects or arrays)". Flatten it, or JSON-encode it into one string `
|
|
96
|
+
+ `field.`, `item: { ${k}: JSON.stringify(...) } // or ${commentText(k)}Id, ${commentText(k)}Name, …`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (inputs?.priority !== undefined && !QUEUE_PRIORITIES.includes(String(inputs.priority))) {
|
|
101
|
+
at('QUEUE_BAD_PRIORITY', `Queue step "${step}"'s \`priority\` is ${JSON.stringify(inputs.priority)}. The node type `
|
|
102
|
+
+ `offers exactly ${QUEUE_PRIORITIES.map((p) => `"${p}"`).join(', ')} — anything else is `
|
|
103
|
+
+ `normalized silently by the platform, so the item is enqueued at the wrong priority `
|
|
104
|
+
+ `rather than rejected.`, `priority: '${nearestName(String(inputs.priority), QUEUE_PRIORITIES) ?? 'Normal'}'`);
|
|
105
|
+
}
|
|
106
|
+
for (const field of ['deferDate', 'dueDate']) {
|
|
107
|
+
const v = inputs?.[field];
|
|
108
|
+
if (typeof v === 'string' && v.trim() !== '' && Number.isNaN(Date.parse(v))) {
|
|
109
|
+
at('QUEUE_BAD_DATE', `Queue step "${step}"'s \`${field}\` is ${JSON.stringify(v)}, which is not a date `
|
|
110
|
+
+ `Orchestrator can read. It takes ISO-8601 — a full timestamp with a zone.`, `${field}: '2026-08-05T17:00:00Z'`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// `returns` describes what a CONSUMER produced, which only exists if the step
|
|
114
|
+
// waited for one. On a fire-and-forget enqueue it is a misunderstanding worth
|
|
115
|
+
// naming: the output is the queue item's own record, and the serializer already
|
|
116
|
+
// declares its fields.
|
|
117
|
+
if (!inputs?.wait && inputs?.returns && Object.keys(inputs.returns).length) {
|
|
118
|
+
warn('QUEUE_RETURNS_WITHOUT_WAIT', `Queue step "${step}" declares \`returns\` but does not \`wait\`, so nothing produces those `
|
|
119
|
+
+ `fields: a fire-and-forget enqueue publishes the queue ITEM's own Orchestrator record `
|
|
120
|
+
+ `(\`Key\`, \`Status\`, \`Reference\`, \`SpecificContent\`, …), whose fields are already `
|
|
121
|
+
+ `declared for you. \`returns\` describes what a CONSUMER wrote into a processed item's `
|
|
122
|
+
+ `result, which needs \`wait: true\`.`, `wait: true`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The three priorities `core.action.queue.create[-and-wait]`'s form offers. */
|
|
126
|
+
const QUEUE_PRIORITIES = ['Low', 'Normal', 'High'];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The Queue family's factory. Moved from core/actions.ts in Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md. */
|
|
2
|
+
import { type ActionSpec } from '../../../core/actions.js';
|
|
3
|
+
import type { QueueItemInputs } from './spec.js';
|
|
4
|
+
/**
|
|
5
|
+
* Declare an Orchestrator QUEUE ITEM step.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A queue is Orchestrator's work list: something puts items on it, and an
|
|
9
|
+
* automation bound to the queue takes them off one at a time. This step is the
|
|
10
|
+
* putting-on half.
|
|
11
|
+
*
|
|
12
|
+
* `queue` + `folderPath` are what the runtime resolves the queue by, and `key` is
|
|
13
|
+
* the queue's own Orchestrator key: it joins this node to the flow's queue
|
|
14
|
+
* bindings, which the serializer emits and points the node at. All three, for the
|
|
15
|
+
* same reason `rpaWorkflow` takes three — the key is the AUTHORING identity, the
|
|
16
|
+
* two bindings are the RUNTIME one.
|
|
17
|
+
*
|
|
18
|
+
* `item` is the work item's data. It becomes the item's `SpecificContent`, and it
|
|
19
|
+
* must be FLAT: Orchestrator rejects a nested object or array outright.
|
|
20
|
+
*
|
|
21
|
+
* **`wait`** picks between the family's two node types, which are one construct
|
|
22
|
+
* with two data paths. Default `false` enqueues and moves on, publishing the queue
|
|
23
|
+
* item's own Orchestrator record — read a field with `out('<step>', 'Key')`, and
|
|
24
|
+
* nothing extra has to be declared because that record's shape is the same for
|
|
25
|
+
* every queue. `wait: true` PARKS the flow until the item has been processed and
|
|
26
|
+
* publishes the consumer's result instead, which is per-automation — so a read of
|
|
27
|
+
* it needs `returns`, exactly as an rpa process's output does.
|
|
28
|
+
*
|
|
29
|
+
* What no rung can tell you: whether anything actually drains this queue. A
|
|
30
|
+
* `wait: true` step on a queue with no consumer waits until it times out.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* **Enqueue a work item and read its Orchestrator key**
|
|
34
|
+
* ```ts
|
|
35
|
+
* .step('enqueue', queueItem({
|
|
36
|
+
* queue: 'SupplierInvoices',
|
|
37
|
+
* folderPath: 'Shared',
|
|
38
|
+
* key: 'fa1a7328-cd17-4525-b5ab-29d86e399312',
|
|
39
|
+
* item: { InvoiceId: input('invoiceRef'), Amount: input('amount') },
|
|
40
|
+
* reference: 'INV-2031',
|
|
41
|
+
* }))
|
|
42
|
+
* .return({ itemKey: out('enqueue', 'Key') })
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param inputs - The `queue` and `folderPath`, the `item` to add, and whether
|
|
46
|
+
* to `wait` for it to be processed.
|
|
47
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function queueItem(inputs: QueueItemInputs): ActionSpec;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** The Queue family's factory. Moved from core/actions.ts in Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md. */
|
|
2
|
+
import { builtinActionSpec } from '../../../core/actions.js';
|
|
3
|
+
/**
|
|
4
|
+
* Declare an Orchestrator QUEUE ITEM step.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* A queue is Orchestrator's work list: something puts items on it, and an
|
|
8
|
+
* automation bound to the queue takes them off one at a time. This step is the
|
|
9
|
+
* putting-on half.
|
|
10
|
+
*
|
|
11
|
+
* `queue` + `folderPath` are what the runtime resolves the queue by, and `key` is
|
|
12
|
+
* the queue's own Orchestrator key: it joins this node to the flow's queue
|
|
13
|
+
* bindings, which the serializer emits and points the node at. All three, for the
|
|
14
|
+
* same reason `rpaWorkflow` takes three — the key is the AUTHORING identity, the
|
|
15
|
+
* two bindings are the RUNTIME one.
|
|
16
|
+
*
|
|
17
|
+
* `item` is the work item's data. It becomes the item's `SpecificContent`, and it
|
|
18
|
+
* must be FLAT: Orchestrator rejects a nested object or array outright.
|
|
19
|
+
*
|
|
20
|
+
* **`wait`** picks between the family's two node types, which are one construct
|
|
21
|
+
* with two data paths. Default `false` enqueues and moves on, publishing the queue
|
|
22
|
+
* item's own Orchestrator record — read a field with `out('<step>', 'Key')`, and
|
|
23
|
+
* nothing extra has to be declared because that record's shape is the same for
|
|
24
|
+
* every queue. `wait: true` PARKS the flow until the item has been processed and
|
|
25
|
+
* publishes the consumer's result instead, which is per-automation — so a read of
|
|
26
|
+
* it needs `returns`, exactly as an rpa process's output does.
|
|
27
|
+
*
|
|
28
|
+
* What no rung can tell you: whether anything actually drains this queue. A
|
|
29
|
+
* `wait: true` step on a queue with no consumer waits until it times out.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* **Enqueue a work item and read its Orchestrator key**
|
|
33
|
+
* ```ts
|
|
34
|
+
* .step('enqueue', queueItem({
|
|
35
|
+
* queue: 'SupplierInvoices',
|
|
36
|
+
* folderPath: 'Shared',
|
|
37
|
+
* key: 'fa1a7328-cd17-4525-b5ab-29d86e399312',
|
|
38
|
+
* item: { InvoiceId: input('invoiceRef'), Amount: input('amount') },
|
|
39
|
+
* reference: 'INV-2031',
|
|
40
|
+
* }))
|
|
41
|
+
* .return({ itemKey: out('enqueue', 'Key') })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param inputs - The `queue` and `folderPath`, the `item` to add, and whether
|
|
45
|
+
* to `wait` for it to be processed.
|
|
46
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
47
|
+
*/
|
|
48
|
+
export function queueItem(inputs) {
|
|
49
|
+
return builtinActionSpec({ kind: 'queueItem', inputs });
|
|
50
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
|
|
2
|
+
import type { QueueItemInputs } from './spec.js';
|
|
3
|
+
/** The spec both queue node types read. */
|
|
4
|
+
export type QueueItemSpec = {
|
|
5
|
+
kind: 'queueItem';
|
|
6
|
+
inputs: QueueItemInputs;
|
|
7
|
+
};
|
|
8
|
+
export declare const queueNodeTypes: FlowVersionedNodeType<QueueItemSpec>[];
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Expr } from '../../../core/expr.js';
|
|
2
|
+
import { jsonClone } from '../../../core/json-file.js';
|
|
3
|
+
import { checkQueueItem } from './check.js';
|
|
4
|
+
import { QUEUE_ITEM_RECORD_SCHEMA } from './record.js';
|
|
5
|
+
const CREATE = 'core.action.queue.create';
|
|
6
|
+
const CREATE_AND_WAIT = 'core.action.queue.create-and-wait';
|
|
7
|
+
/**
|
|
8
|
+
* Build the definition for one queue-item node.
|
|
9
|
+
*
|
|
10
|
+
* Unlike `rpaWorkflowDef`, this one starts from the BUNDLED definition — the queue
|
|
11
|
+
* node types are generic (no tenant key in the type), so the platform's own copy
|
|
12
|
+
* can be shipped and is: `core-definitions.json` carries both at `@1.2`, verbatim
|
|
13
|
+
* from the catalogue the running `uip` consults (`@uipath/flow-core/manifest`'s
|
|
14
|
+
* `ootbNodes`). A test pins that the emitted definition is that copy plus exactly
|
|
15
|
+
* the three per-instance edits below, so the bundled snapshot stays diffable
|
|
16
|
+
* against upstream.
|
|
17
|
+
*
|
|
18
|
+
* VERSION 1.2, not the 1.0 the bundled sample carries. The platform is two
|
|
19
|
+
* migrations on, and both are semantic: 1.0 → 1.1 re-types `itemData` from
|
|
20
|
+
* `string` to `object`, and 1.1 → 1.2 makes the field validate as an object
|
|
21
|
+
* expression. `shared/sample-flows/queue-operations.flow` is v1.0-era (a JSON
|
|
22
|
+
* STRING in `itemData`) and dated 2026-05-20 — the shape the platform no longer
|
|
23
|
+
* writes. Reading it for the surface would have shipped the wrong field TYPE.
|
|
24
|
+
*
|
|
25
|
+
* The three per-instance edits:
|
|
26
|
+
*
|
|
27
|
+
* - `model.bindings.resourceKey` — the queue's own Orchestrator key. The shipped
|
|
28
|
+
* definition ships it BLANK, because only an instance knows which queue.
|
|
29
|
+
* - `model.context` — the `=bindings.<id>` references. The definition ships
|
|
30
|
+
* `<bindings.name>` placeholders; an instance names the actual binding ids,
|
|
31
|
+
* which is the second way `resourceBindings` can be resolved and the
|
|
32
|
+
* shape the bundled sample has.
|
|
33
|
+
* - `outputDefinition.output.schema` — the queue item's record for a plain
|
|
34
|
+
* create, or the author's `returns` for a create-and-wait. Without a schema a
|
|
35
|
+
* downstream read is unresolvable.
|
|
36
|
+
*/
|
|
37
|
+
function queueItemDef(spec, nodeType, stepName, ids, base) {
|
|
38
|
+
// The template at the version being emitted: the registry resolved it exactly
|
|
39
|
+
// (an unregistered pin is NODE_VERSION_UNREGISTERED before this runs).
|
|
40
|
+
if (!base)
|
|
41
|
+
throw new Error(`no bundled definition for "${nodeType}"`);
|
|
42
|
+
const def = jsonClone(base);
|
|
43
|
+
def.model.bindings.resourceKey = spec.key;
|
|
44
|
+
def.model.context = [
|
|
45
|
+
{ name: 'name', type: 'string', value: `=bindings.${ids.name}`, default: spec.queue },
|
|
46
|
+
{ name: 'folderPath', type: 'string', value: `=bindings.${ids.folderPath}`, default: spec.folderPath },
|
|
47
|
+
{ name: '_label', type: 'string', value: stepName },
|
|
48
|
+
];
|
|
49
|
+
const schema = spec.wait
|
|
50
|
+
? (spec.returns && Object.keys(spec.returns).length
|
|
51
|
+
? {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
|
|
54
|
+
}
|
|
55
|
+
: undefined)
|
|
56
|
+
: jsonClone(QUEUE_ITEM_RECORD_SCHEMA);
|
|
57
|
+
if (schema)
|
|
58
|
+
def.outputDefinition.output.schema = schema;
|
|
59
|
+
return def;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The two bindings a queue reference carries — the same `name` + `folderPath` pair
|
|
63
|
+
* the process families use, keyed to the queue's own Orchestrator key rather than
|
|
64
|
+
* to a `<folderPath>.<name>` string. That is what the only queue instance the repo
|
|
65
|
+
* has (`sample-flows/queue-operations.flow`) uses, and it is the queue's stable
|
|
66
|
+
* identity: a queue can be renamed, its key cannot.
|
|
67
|
+
*
|
|
68
|
+
* ONE PAIR PER QUEUE, not per step. Bindings are addressed by
|
|
69
|
+
* `resource + resourceKey + propertyAttribute` — that is the key a bindings
|
|
70
|
+
* lookup map is built on — so a second pair for the same queue is not a second
|
|
71
|
+
* reference, it is a duplicate that shadows the first: both nodes resolve to
|
|
72
|
+
* whichever was inserted last, and the other pair sits in the file referenced by
|
|
73
|
+
* nothing. So two steps on the same queue SHARE these, and the ids come from the
|
|
74
|
+
* queue rather than from a step name.
|
|
75
|
+
*
|
|
76
|
+
* (The rpa family mints its pair from the STEP name, so two steps starting the same
|
|
77
|
+
* process emit the same duplicate-and-shadow shape. Nothing exercises it today —
|
|
78
|
+
* every rpa flow in the repo has one process step — and changing those ids would
|
|
79
|
+
* rewrite the rpa goldens, so it is recorded as a finding rather than fixed here.)
|
|
80
|
+
*/
|
|
81
|
+
function queueItemBindings(spec, existing) {
|
|
82
|
+
const found = (attr) => [...existing.values()].find((b) => b.resource === 'queue' && b.resourceKey === spec.key && b.propertyAttribute === attr)?.id;
|
|
83
|
+
const already = { name: found('name'), folderPath: found('folderPath') };
|
|
84
|
+
if (already.name && already.folderPath) {
|
|
85
|
+
return { ids: { name: already.name, folderPath: already.folderPath }, entries: [] };
|
|
86
|
+
}
|
|
87
|
+
// A readable, stable id per queue. Two queues that share a NAME across folders
|
|
88
|
+
// would collide, so the id is suffixed until it is free.
|
|
89
|
+
const slug = spec.queue.replace(/[^A-Za-z0-9]/g, '') || 'Queue';
|
|
90
|
+
let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
|
|
91
|
+
for (let n = 2; existing.has(`${base}Name`) || existing.has(`${base}FolderPath`); n++) {
|
|
92
|
+
base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}${n}`;
|
|
93
|
+
}
|
|
94
|
+
const ids = { name: `${base}Name`, folderPath: `${base}FolderPath` };
|
|
95
|
+
return {
|
|
96
|
+
ids,
|
|
97
|
+
entries: [
|
|
98
|
+
{
|
|
99
|
+
id: ids.name,
|
|
100
|
+
name: 'name',
|
|
101
|
+
type: 'string',
|
|
102
|
+
resource: 'queue',
|
|
103
|
+
resourceKey: spec.key,
|
|
104
|
+
default: spec.queue,
|
|
105
|
+
propertyAttribute: 'name',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: ids.folderPath,
|
|
109
|
+
name: 'folderPath',
|
|
110
|
+
type: 'string',
|
|
111
|
+
resource: 'queue',
|
|
112
|
+
resourceKey: spec.key,
|
|
113
|
+
default: spec.folderPath,
|
|
114
|
+
propertyAttribute: 'folderPath',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** A read of a queue step's output: `QUEUE_READ_*`, in the wording the rule had before the port. */
|
|
120
|
+
const read = (wait) => ({
|
|
121
|
+
codePrefix: 'QUEUE',
|
|
122
|
+
label: wait ? 'Queue wait' : 'Queue item',
|
|
123
|
+
whenUndeclared: `What the consumer writes into the processed item's result is that automation's business; `
|
|
124
|
+
+ `declare the fields you read.`,
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* A plain enqueue publishes the queue ITEM's Orchestrator record (fixed, from
|
|
128
|
+
* `QUEUE_ITEM_RECORD_SCHEMA`); a `wait: true` publishes the processed result,
|
|
129
|
+
* whose fields only the author's `returns` states.
|
|
130
|
+
*/
|
|
131
|
+
function queueOutput(inputs, wait) {
|
|
132
|
+
if (!wait) {
|
|
133
|
+
const fields = Object.fromEntries(Object.entries(QUEUE_ITEM_RECORD_SCHEMA.properties)
|
|
134
|
+
.map(([k, v]) => [k, v.type ?? 'any']));
|
|
135
|
+
return { kind: 'fields', fields, read: read(false) };
|
|
136
|
+
}
|
|
137
|
+
const returns = inputs?.returns ?? {};
|
|
138
|
+
return {
|
|
139
|
+
kind: 'fields',
|
|
140
|
+
fields: Object.keys(returns).length ? returns : undefined,
|
|
141
|
+
read: read(true),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The `returns` a create-and-wait was authored with, from the field map
|
|
146
|
+
* `queueItemDef` wrote into `outputDefinition.output.schema` and `makeNode`
|
|
147
|
+
* copied onto this node's own `outputs`. Read from the NODE, not the file's
|
|
148
|
+
* `definitions[]` entry: that holds one copy per `<type>@<version>` (the last
|
|
149
|
+
* step's), so two wait steps would share one schema. Undefined when the node
|
|
150
|
+
* carries no field map, which is what an omitted `returns` emits.
|
|
151
|
+
*/
|
|
152
|
+
function liftReturns(node) {
|
|
153
|
+
const props = node
|
|
154
|
+
?.outputs?.output?.schema?.properties;
|
|
155
|
+
if (!props || typeof props !== 'object')
|
|
156
|
+
return undefined;
|
|
157
|
+
const entries = Object.entries(props)
|
|
158
|
+
.filter(([, v]) => typeof v?.type === 'string')
|
|
159
|
+
.map(([k, v]) => [k, v.type]);
|
|
160
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
161
|
+
}
|
|
162
|
+
function queueAt(typeName, wait) {
|
|
163
|
+
return {
|
|
164
|
+
kind: 'queueItem', typeName, version: '1.2', factory: 'queueItem', definition: 'per-tenant', errorPort: true,
|
|
165
|
+
rawNode: { factory: wait ? 'queueItem({ wait: true })' : 'queueItem()' },
|
|
166
|
+
serialize: ({ inputs: s }, ctx) => {
|
|
167
|
+
// The bindings first: two steps on the same queue share one pair, and the
|
|
168
|
+
// node's `model.context` has to name the ids that actually exist.
|
|
169
|
+
const { ids, entries } = queueItemBindings(s, ctx.bindings);
|
|
170
|
+
for (const b of entries)
|
|
171
|
+
ctx.bind(b);
|
|
172
|
+
const manifest = queueItemDef(s, typeName, ctx.step, ids, ctx.definition(typeName, ctx.version));
|
|
173
|
+
return {
|
|
174
|
+
definition: { source: 'snapshot', nodeType: typeName, version: ctx.version, manifest },
|
|
175
|
+
// The result is read as `$vars.<step>.output`, like every other family: the
|
|
176
|
+
// node's own output block maps the runtime `response` onto the variable
|
|
177
|
+
// `output` (`source: "=response", var: "output"`), and the product honours
|
|
178
|
+
// that mapping for both queue variants. The node KEEPS its copy of the model,
|
|
179
|
+
// as every core action node with a model does.
|
|
180
|
+
inputs: {
|
|
181
|
+
queue: { name: s.queue, folderPath: s.folderPath, key: s.key },
|
|
182
|
+
// `itemData`, not `item`: the surface's name is friendlier, the node's is
|
|
183
|
+
// the platform's. Rendered one level deep, as the item is FLAT.
|
|
184
|
+
itemData: Object.fromEntries(Object.entries(s.item ?? {}).map(([k, v]) => [k, ctx.render(v)])),
|
|
185
|
+
priority: s.priority ?? 'Normal',
|
|
186
|
+
reference: s.reference === undefined ? '' : ctx.render(s.reference),
|
|
187
|
+
deferDate: s.deferDate === undefined ? '' : ctx.render(s.deferDate),
|
|
188
|
+
dueDate: s.dueDate === undefined ? '' : ctx.render(s.dueDate),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
deserialize: (node, ctx) => {
|
|
193
|
+
const inputs = node.inputs ?? {};
|
|
194
|
+
const q = (inputs.queue ?? {});
|
|
195
|
+
// A value as the author wrote it: an expression back to an Expr, a literal
|
|
196
|
+
// string as itself, any other JSON as is.
|
|
197
|
+
const value = (raw) => {
|
|
198
|
+
const text = ctx.sourceText(raw);
|
|
199
|
+
if (text === undefined)
|
|
200
|
+
return raw;
|
|
201
|
+
return text.startsWith('=js:') ? ctx.expr(text) : text;
|
|
202
|
+
};
|
|
203
|
+
// serialize writes '' for an omitted optional; that is not an authored value.
|
|
204
|
+
const optional = (raw) => {
|
|
205
|
+
const text = ctx.sourceText(raw);
|
|
206
|
+
return text === undefined || text === '' ? undefined : value(raw);
|
|
207
|
+
};
|
|
208
|
+
const priority = ctx.plainValue(inputs.priority);
|
|
209
|
+
// A wait's `returns` is the schema serialize wrote for THIS step (the
|
|
210
|
+
// template ships none), as the node's own `outputs` carries it.
|
|
211
|
+
const returns = wait ? liftReturns(node) : undefined;
|
|
212
|
+
return {
|
|
213
|
+
kind: 'queueItem',
|
|
214
|
+
inputs: {
|
|
215
|
+
queue: q.name, folderPath: q.folderPath, key: q.key,
|
|
216
|
+
item: Object.fromEntries(Object.entries((inputs.itemData ?? {}))
|
|
217
|
+
.map(([k, v]) => [k, value(v)])),
|
|
218
|
+
...(typeof priority === 'string' ? { priority: priority } : {}),
|
|
219
|
+
...(optional(inputs.reference) !== undefined ? { reference: optional(inputs.reference) } : {}),
|
|
220
|
+
...(optional(inputs.deferDate) !== undefined ? { deferDate: optional(inputs.deferDate) } : {}),
|
|
221
|
+
...(optional(inputs.dueDate) !== undefined ? { dueDate: optional(inputs.dueDate) } : {}),
|
|
222
|
+
...(wait ? { wait: true } : {}),
|
|
223
|
+
...(returns ? { returns } : {}),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
print: ({ inputs }, ctx) => {
|
|
228
|
+
const rows = Object.entries(inputs.item ?? {});
|
|
229
|
+
return {
|
|
230
|
+
factory: 'queueItem',
|
|
231
|
+
options: [
|
|
232
|
+
['queue', ctx.string(String(inputs.queue ?? ''))],
|
|
233
|
+
['folderPath', ctx.string(String(inputs.folderPath ?? ''))],
|
|
234
|
+
['key', ctx.string(String(inputs.key ?? ''))],
|
|
235
|
+
// An expression as the helper an author writes; any other value as the data
|
|
236
|
+
// the node stores, verbatim (a nested value is refused by check, but kept).
|
|
237
|
+
['item', rows.length
|
|
238
|
+
? `{ ${rows.map(([k, v]) => `${ctx.key(k)}: ${v instanceof Expr ? ctx.value(v) : ctx.json(v)}`).join(', ')} }`
|
|
239
|
+
: '{}'],
|
|
240
|
+
// `Normal` is the definition's default — only a departure was authored.
|
|
241
|
+
['priority', inputs.priority !== undefined && inputs.priority !== 'Normal' ? ctx.string(inputs.priority) : undefined],
|
|
242
|
+
['reference', inputs.reference === undefined ? undefined : ctx.value(inputs.reference)],
|
|
243
|
+
['deferDate', inputs.deferDate === undefined ? undefined : ctx.value(inputs.deferDate)],
|
|
244
|
+
['dueDate', inputs.dueDate === undefined ? undefined : ctx.value(inputs.dueDate)],
|
|
245
|
+
['wait', wait ? 'true' : undefined],
|
|
246
|
+
['returns', inputs.returns && Object.keys(inputs.returns).length
|
|
247
|
+
? `{ ${Object.entries(inputs.returns).map(([k, t]) => `${ctx.key(k)}: ${ctx.string(String(t))}`).join(', ')} }`
|
|
248
|
+
: undefined],
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
check: ({ inputs }, ctx) => checkQueueItem(inputs, ctx),
|
|
253
|
+
// check collected no expressions for a queue step before the port; unchanged by it.
|
|
254
|
+
expressions: () => [],
|
|
255
|
+
output: ({ inputs }) => queueOutput(inputs, wait),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function queueType(typeName, wait) {
|
|
259
|
+
return {
|
|
260
|
+
kind: 'queueItem', typeName, factory: 'queueItem', defaultVersion: '1.2',
|
|
261
|
+
versions: { '1.2': queueAt(typeName, wait) },
|
|
262
|
+
matches: ({ inputs }) => Boolean(inputs?.wait) === wait,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
export const queueNodeTypes = [queueType(CREATE, false), queueType(CREATE_AND_WAIT, true)];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Queue family's authoring types (`core.action.queue.create` and
|
|
3
|
+
* `core.action.queue.create-and-wait`), and its kind's entry in
|
|
4
|
+
* `FlowActionSpecs`. Moved from core/actions.ts in Phase 4 of
|
|
5
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReturnFieldType } from '../../../core/actions.js';
|
|
8
|
+
import type { Expr } from '../../../core/expr.js';
|
|
9
|
+
/** The three priorities an Orchestrator queue item can carry. */
|
|
10
|
+
export type QueuePriority = 'Low' | 'Normal' | 'High';
|
|
11
|
+
export interface QueueItemInputs {
|
|
12
|
+
/** The queue's name in Orchestrator — e.g. `'SupplierInvoices'`. */
|
|
13
|
+
queue: string;
|
|
14
|
+
/** The Orchestrator folder holding the queue — e.g. `'Shared'`. */
|
|
15
|
+
folderPath: string;
|
|
16
|
+
/**
|
|
17
|
+
* The queue's own Orchestrator key (a GUID). It is the JOIN between this node
|
|
18
|
+
* and the flow's queue bindings — a node which does not reference its bindings
|
|
19
|
+
* cannot be dispatched — and it is what
|
|
20
|
+
* the designer's queue picker writes. The RUNTIME resolves the queue by
|
|
21
|
+
* `queue` + `folderPath`, so all three are needed and none is redundant.
|
|
22
|
+
*
|
|
23
|
+
* Find it with `uip or queues list --all-folders`.
|
|
24
|
+
*/
|
|
25
|
+
key: string;
|
|
26
|
+
/**
|
|
27
|
+
* The work item's own data: a FLAT object of primitive values. It becomes the
|
|
28
|
+
* item's `SpecificContent`, and Orchestrator rejects a nested object or array
|
|
29
|
+
* with "Property values must be simple (like numbers or text)".
|
|
30
|
+
*/
|
|
31
|
+
item: Record<string, unknown>;
|
|
32
|
+
/** Processing priority. Defaults to `'Normal'`, as the node type does. */
|
|
33
|
+
priority?: QueuePriority;
|
|
34
|
+
/**
|
|
35
|
+
* A reference string for the item. Optional in general — but a queue created
|
|
36
|
+
* with "enforce unique reference" REQUIRES one and rejects a repeat with
|
|
37
|
+
* `HTTP 409: Duplicate Reference`, so derive it from the flow's input rather
|
|
38
|
+
* than hard-coding it.
|
|
39
|
+
*
|
|
40
|
+
* Which is why it takes an {@link Expr} as well as a literal: the serializer
|
|
41
|
+
* renders it through the same path as any other value, so
|
|
42
|
+
* `reference: input('invoiceRef')` and
|
|
43
|
+
* ``reference: js`${input('invoiceRef')} + "-review"` `` both emit. Typing it
|
|
44
|
+
* as a bare `string` contradicted the advice directly above and stopped two
|
|
45
|
+
* of the SDK's own examples compiling.
|
|
46
|
+
*/
|
|
47
|
+
reference?: string | Expr;
|
|
48
|
+
/** Earliest the item may be processed, ISO-8601 (e.g. `'2026-08-01T09:00:00Z'`). */
|
|
49
|
+
deferDate?: string;
|
|
50
|
+
/** Latest the item should be processed, ISO-8601. What an SLA is measured against. */
|
|
51
|
+
dueDate?: string;
|
|
52
|
+
/**
|
|
53
|
+
* `false` (the default) enqueues and moves on — `core.action.queue.create`.
|
|
54
|
+
* `true` PARKS the flow until whatever consumes the queue has processed the
|
|
55
|
+
* item, and publishes that result — `core.action.queue.create-and-wait`.
|
|
56
|
+
*
|
|
57
|
+
* The two are one construct with two data paths, so this is one field rather
|
|
58
|
+
* than two factories. Waiting is not free: nothing completes the item but a
|
|
59
|
+
* consumer (an RPA process bound to the queue), so a flow that waits on a queue
|
|
60
|
+
* nobody drains waits forever.
|
|
61
|
+
*/
|
|
62
|
+
wait?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Only for `wait: true`: the fields the PROCESSED item's result carries, and
|
|
65
|
+
* their types (e.g. `{ approved: 'boolean' }`). Required if anything reads the
|
|
66
|
+
* step's output.
|
|
67
|
+
*
|
|
68
|
+
* Why you have to say: what a consumer writes into a transaction's `Output` is
|
|
69
|
+
* that automation's business, not the queue's — no definition declares it and
|
|
70
|
+
* authoring happens offline. Without it `$vars.<step>.output.<field>` reads from
|
|
71
|
+
* an object with no declared fields, and nothing could tell a real field from
|
|
72
|
+
* a typo.
|
|
73
|
+
*
|
|
74
|
+
* `wait: false` needs none: what it publishes is the queue ITEM's own
|
|
75
|
+
* Orchestrator record (`Key`, `Status`, `Reference`, `SpecificContent`, …),
|
|
76
|
+
* which is the same for every queue, so the serializer declares it for you.
|
|
77
|
+
*
|
|
78
|
+
* @enforcedBy QUEUE_READ_UNKNOWN_FIELD A read must name a field of the record
|
|
79
|
+
* (`wait: false`) or of this `returns` (`wait: true`).
|
|
80
|
+
* @enforcedBy QUEUE_READ_WITHOUT_RETURNS Reading a field off a `wait: true` result
|
|
81
|
+
* requires declaring it here.
|
|
82
|
+
*/
|
|
83
|
+
returns?: Record<string, ReturnFieldType>;
|
|
84
|
+
}
|
|
85
|
+
declare module '../../../core/actions.js' {
|
|
86
|
+
interface FlowActionSpecs {
|
|
87
|
+
/** The spec {@link queueItem} builds. */
|
|
88
|
+
queueItem: {
|
|
89
|
+
inputs: QueueItemInputs;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|