@uipath/maestro-builder-sdk 6.71.1 → 6.72.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 CHANGED
@@ -37,7 +37,7 @@ could hand you spans for a version you do not have.
37
37
 
38
38
  ## `@uipath/maestro-builder-sdk`
39
39
 
40
- 245 symbol(s). Import from `@uipath/maestro-builder-sdk`.
40
+ 247 symbol(s). Import from `@uipath/maestro-builder-sdk`.
41
41
 
42
42
  | Symbol | Kind | Declaration | Area | Summary |
43
43
  | --- | --- | --- | --- | --- |
@@ -143,10 +143,12 @@ could hand you spans for a version you do not have.
143
143
  | `FlowActionSpecs` | interface | `dist/core/actions.d.ts:813-846` | Supporting types | Every action kind, keyed by kind, to the body of its spec — the arm's fields other than `kind`. |
144
144
  | `FlowBuilder` | class | `dist/flow-sdk.d.ts:1411-1791` | Builders | 15 member(s) |
145
145
  | `FlowLayout` | interface | `dist/flow-sdk.d.ts:830-843` | Supporting types | Designer layout for a flow, keyed by AUTHOR-level ids: node entries by step name (plus the trigger id and `end`), edge… |
146
+ | `FlowMetadata` | interface | `dist/nodes/script/flow/spec.d.ts:63-95` | Actions | The instance metadata, as a script function's second parameter sees it. |
146
147
  | `FlowNode` | class | `dist/core/node-classes.d.ts:126-161` | Supporting types | The abstract root of every definition-backed node the builder can place. |
147
148
  | `FlowResource` | class | `dist/core/node-classes.d.ts:176-183` | Supporting types | A node attached to another semantic node — an inline agent's tool, context, memory, or escalation. |
148
149
  | `FlowTrigger` | class | `dist/core/node-classes.d.ts:169-175` | Supporting types | A node that starts the flow — pass an instance to `.trigger(…)`. |
149
150
  | `FlowTypeDesc` | type | `dist/flow-types.d.ts:45-51` | Supporting types | A `types.*` descriptor a Flow variable may declare: every member of `types` but the Case-only `float`, `double`, `date`,… |
151
+ | `FlowVars` | type | `dist/nodes/script/flow/spec.d.ts:45-62` | Actions | The runtime's variable scope, as a script function's parameter sees it. |
150
152
  | `FormField` | type | `dist/core/actions.d.ts:259` | Supporting types | |
151
153
  | `FormFieldCondition` | interface | `dist/core/actions.d.ts:113-133` | Supporting types | One condition of a form-field rule (`@uipath/hitl-schema-types` `fieldConditionSchema`): `when` names the field it… |
152
154
  | `FormFieldControlType` | type | `dist/core/actions.d.ts:105-112` | Supporting types | How a form field renders (`@uipath/hitl-schema-types` `controlType`). |
@@ -236,14 +238,14 @@ could hand you spans for a version you do not have.
236
238
  | `ScheduledInputs` | interface | `dist/flow-sdk.d.ts:153-165` | Option shapes | 1 field(s) |
237
239
  | `ScheduleEvery` | type | `dist/flow-sdk.d.ts:141-152` | Supporting types | How often a scheduled flow runs, as an ISO-8601 repeating interval: `R/` followed by a duration. |
238
240
  | `SchedulePreset` | type | `dist/flow-sdk.d.ts:140` | Supporting types | |
239
- | `script` | function | `dist/nodes/script/flow/factories.d.ts:8-36` | Actions | Declare an inline script action, as a TypeScript function the compiler checks. |
240
- | `script` | function | `dist/nodes/script/flow/factories.d.ts:37-47` | Actions | Declare an inline script action, as a TypeScript function, with no declared return: the body's type is inferred as it is… |
241
- | `script` | function | `dist/nodes/script/flow/factories.d.ts:48-55` | Actions | Declare an inline script action. |
242
- | `ScriptInputs` | interface | `dist/nodes/script/flow/spec.d.ts:7-44` | Option shapes | 2 field(s) |
241
+ | `script` | function | `dist/nodes/script/flow/factories.d.ts:8-38` | Actions | Declare an inline script action, as a TypeScript function the compiler checks. |
242
+ | `script` | function | `dist/nodes/script/flow/factories.d.ts:39-49` | Actions | Declare an inline script action, as a TypeScript function, with no declared return: the body's type is inferred as it is… |
243
+ | `script` | function | `dist/nodes/script/flow/factories.d.ts:50-57` | Actions | Declare an inline script action. |
244
+ | `ScriptInputs` | interface | `dist/nodes/script/flow/spec.d.ts:7-44` | Actions | 2 field(s) |
243
245
  | `ScriptReturns` | type | `dist/core/script-return.d.ts:42-48` | Supporting types | What an author may write for `script({ returns })`: the type outright, or — for an object return — the FIELDS, which is… |
244
246
  | `ScriptReturnType` | type | `dist/core/script-return.d.ts:31-41` | Supporting types | The vocabulary a script's output can be declared as. |
245
- | `ScriptValue` | type | `dist/nodes/script/flow/spec.d.ts:65-75` | Supporting types | What a script function must return for a given `returns`: the type a name stands for, or, for a field map, an object… |
246
- | `ScriptValueOf` | type | `dist/nodes/script/flow/spec.d.ts:63-64` | Supporting types | The TypeScript type one of ScriptReturnType's names stands for. |
247
+ | `ScriptValue` | type | `dist/nodes/script/flow/spec.d.ts:98-108` | Actions | What a script function must return for a given `returns`: the type a name stands for, or, for a field map, an object… |
248
+ | `ScriptValueOf` | type | `dist/nodes/script/flow/spec.d.ts:96-97` | Actions | The TypeScript type one of ScriptReturnType's names stands for. |
247
249
  | `sendMessage` | function | `dist/nodes/conversation/flow/factories.d.ts:8-26` | Actions | Post an assistant message into a live conversation (`uipath.conversational.send-message`). |
248
250
  | `SendMessageInputs` | interface | `dist/nodes/conversation/flow/spec.d.ts:8-27` | Option shapes | 4 field(s) |
249
251
  | `ShownField` | interface | `dist/core/actions.d.ts:205-220` | Supporting types | A value the reviewer READS: shown on the form, filled in from the flow. |
@@ -5,13 +5,13 @@
5
5
 
6
6
  # Maestro Builder SDK — member index
7
7
 
8
- **A lookup table of 1238 rows — match one name in it, do not read it.**
8
+ **A lookup table of 1243 rows — match one name in it, do not read it.**
9
9
  Grep or search for the member you have; the row it lands on is the whole
10
10
  answer. Reading top to bottom costs ~20x what the one row does and tells you
11
11
  nothing you asked for.
12
12
 
13
- Every field and method of every exported shape — 1238 across
14
- 228 interfaces and classes — keyed by the MEMBER's own name.
13
+ Every field and method of every exported shape — 1243 across
14
+ 229 interfaces and classes — keyed by the MEMBER's own name.
15
15
 
16
16
  **Use this when the name you have is a field or a method**, which is usually
17
17
  the case: the reference pages are "one line per field", so you arrive holding
@@ -28,7 +28,7 @@ truncated summary would, for less. Read the span.
28
28
  Member names repeat across shapes (`name` is declared on 69 of them), so the
29
29
  `Declared on` column is what makes a match actionable. For the whole shape
30
30
  rather than the one field, look that name up in [`api-index.md`](api-index.md) —
31
- which is why its span is not repeated on all 1238 rows here.
31
+ which is why its span is not repeated on all 1243 rows here.
32
32
 
33
33
  **A name that misses both indexes is probably not part of the API.** The one
34
34
  that comes up is a RUNTIME output key — a human task's `Action`, a step's
@@ -299,6 +299,7 @@ not find it either.
299
299
  | `effects` | property | `FormFieldRule` | `dist/core/actions.d.ts:159-160` |
300
300
  | `element` | property | `PlainTaskOpts` | `dist/bpmn/bpmn-sdk.d.ts:1712-1713` |
301
301
  | `element` | property | `RegistryTypeSpec` | `dist/bpmn/typed-node.d.ts:87-88` |
302
+ | `ElementCount` | property | `FlowMetadata` | `dist/nodes/script/flow/spec.d.ts:93-94` |
302
303
  | `elementId` | property | `BpmnVarDecl` | `dist/bpmn/bpmn-sdk.d.ts:396-403` |
303
304
  | `elementId` | property | `VarOpts` | `dist/bpmn/bpmn-sdk.d.ts:1741-1764` |
304
305
  | `enabledForEvals` | property | `BuiltInValidatorGuardrail` | `dist/core/actions.d.ts:360` |
@@ -372,6 +373,7 @@ not find it either.
372
373
  | `external` | property | `BuiltTask` | `dist/case/case-sdk.d.ts:720-721` |
373
374
  | `externalAgent` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2942-2978` |
374
375
  | `externalAgent` | method | `TaskBuilder` | `dist/case/case-sdk.d.ts:1095-1102` |
376
+ | `ExternalId` | property | `FlowMetadata` | `dist/nodes/script/flow/spec.d.ts:91-92` |
375
377
  | `externalWorkflow` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2979-2995` |
376
378
  | `externalWorkflow` | method | `TaskBuilder` | `dist/case/case-sdk.d.ts:1103-1110` |
377
379
  | `extractionResult` | property | `DocValidationInputs` | `dist/nodes/hitl/flow/spec.d.ts:82-87` |
@@ -424,6 +426,7 @@ not find it either.
424
426
  | `folderKey` | property | `McpToolRef` | `dist/core/actions.d.ts:457-458` |
425
427
  | `folderKey` | property | `OrchestratorOpts` | `dist/bpmn/bpmn-sdk.d.ts:1300-1308` |
426
428
  | `folderKey` | property | `RemoteA2aToolRef` | `dist/core/actions.d.ts:489` |
429
+ | `FolderKey` | property | `FlowMetadata` | `dist/nodes/script/flow/spec.d.ts:85-86` |
427
430
  | `folderName` | property | `IxpExtractInputs` | `dist/nodes/document/flow/spec.d.ts:108-114` |
428
431
  | `folderPath` | property | `AgenticProcessInputsBase` | `dist/nodes/process/flow/spec.d.ts:214-221` |
429
432
  | `folderPath` | property | `AgentInputs` | `dist/nodes/process/flow/spec.d.ts:317-328` |
@@ -542,6 +545,7 @@ not find it either.
542
545
  | `inputSchema` | property | `AppRef` | `dist/nodes/hitl/flow/spec.d.ts:245-253` |
543
546
  | `inputSchema` | property | `ScriptTaskOpts` | `dist/bpmn/bpmn-sdk.d.ts:953-964` |
544
547
  | `inputTarget` | property | `RegistryTypeSpec` | `dist/bpmn/typed-node.d.ts:93` |
548
+ | `InstanceId` | property | `FlowMetadata` | `dist/nodes/script/flow/spec.d.ts:87-88` |
545
549
  | `intermediateCatchEvent` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2326-2333` |
546
550
  | `intermediateThrowEvent` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2334-2341` |
547
551
  | `internal` | property | `BpmnVarDecl` | `dist/bpmn/bpmn-sdk.d.ts:410-411` |
@@ -883,6 +887,7 @@ not find it either.
883
887
  | `process` | property | `OrchestratorOpts` | `dist/bpmn/bpmn-sdk.d.ts:1267-1285` |
884
888
  | `process` | method | `TaskBuilder` | `dist/case/case-sdk.d.ts:959-968` |
885
889
  | `processId` | property | `SugarRegistry` | `dist/bpmn/bpmn-sdk.d.ts:2060-2065` |
890
+ | `ProcessKey` | property | `FlowMetadata` | `dist/nodes/script/flow/spec.d.ts:89-90` |
886
891
  | `project` | property | `IxpExtractInputs` | `dist/nodes/document/flow/spec.d.ts:76-89` |
887
892
  | `projectId` | property | `AgenticProcessInputsBase` | `dist/nodes/process/flow/spec.d.ts:222-229` |
888
893
  | `projectId` | property | `AgentInputs` | `dist/nodes/process/flow/spec.d.ts:329-334` |
@@ -1010,7 +1015,7 @@ not find it either.
1010
1015
  | `schemaVersion` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1604-1643` |
1011
1016
  | `scope` | property | `EscalationRecipient` | `dist/case/case-sdk.d.ts:160` |
1012
1017
  | `scopes` | property | `GuardrailSelector` | `dist/core/actions.d.ts:290` |
1013
- | `script` | property | `FlowActionSpecs` | `dist/nodes/script/flow/spec.d.ts:78-81` |
1018
+ | `script` | property | `FlowActionSpecs` | `dist/nodes/script/flow/spec.d.ts:111-114` |
1014
1019
  | `script` | property | `ScriptTaskOpts` | `dist/bpmn/bpmn-sdk.d.ts:880-896` |
1015
1020
  | `scriptFormat` | property | `ScriptTaskOpts` | `dist/bpmn/bpmn-sdk.d.ts:897-898` |
1016
1021
  | `scriptTask` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2387-2395` |
package/dist/check.js CHANGED
@@ -24,6 +24,7 @@ import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
24
24
  import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
25
25
  import { planSharedInputs, sharedInputRoots } from './flow-variables.js';
26
26
  import { VARS_READ_RE } from './core/vars-read.js';
27
+ import { FLOW_METADATA_KEYS, unknownMetadataKeys } from './core/flow-metadata.js';
27
28
  import { endContentKey } from './flow-end-content.js';
28
29
  import { FLOW_NODE_TYPES } from './flow-node-registry.js';
29
30
  import { stepExits } from './flow-step-exits.js';
@@ -942,6 +943,24 @@ function checkFlow(built, ancestors, opts = {}) {
942
943
  }
943
944
  }
944
945
  }
946
+ // A `$metadata` key the engine does not set. Its shape is fixed, unlike
947
+ // `$vars` (core/flow-metadata.ts), so a typo (`$metadata.instanceId`) reads
948
+ // `undefined`. A warning, not an error: a case can set other keys on an
949
+ // instance it starts, and the flow cannot tell whether one does. Read from
950
+ // the parsed code, so a local `$metadata` or a comment is not a read.
951
+ for (const { js, step } of exprs) {
952
+ for (const key of unknownMetadataKeys(js)) {
953
+ const near = FLOW_METADATA_KEYS.find((k) => k.toLowerCase() === key.toLowerCase());
954
+ diags.push({
955
+ level: 'warning',
956
+ code: 'UNKNOWN_METADATA_KEY',
957
+ step,
958
+ message: `"${step}" reads $metadata.${key}, which the engine does not set: it sets ${FLOW_METADATA_KEYS.join(', ')}. `
959
+ + 'Unless a case that starts this flow sets it, it reads undefined.',
960
+ ...(near ? { suggestion: `$metadata.${near}` } : {}),
961
+ });
962
+ }
963
+ }
945
964
  // A reference that reaches a step through a BRACKET instead of a dot.
946
965
  //
947
966
  // Both forms resolve at run time — measured, `$vars.call["output"].x` and
@@ -0,0 +1,18 @@
1
+ /** The keys the engine sets on every instance's `$metadata`. */
2
+ export declare const FLOW_METADATA_KEYS: readonly string[];
3
+ /**
4
+ * Every `$metadata` key `code` reads by name, in source order: a property
5
+ * access (`$metadata.InstanceId`, `?.`, with any whitespace or comment around
6
+ * the dot) or a string-literal element access (`$metadata['InstanceId']`).
7
+ *
8
+ * Read from the parsed code, with names resolved by a one-file checker, so only
9
+ * the runtime's own global counts: a local or parameter named `$metadata`, a
10
+ * member of that name (`o.$metadata.Region`), and text in a comment or string
11
+ * are not reads. A computed key reads nothing knowable and is skipped.
12
+ *
13
+ * `code` is a script body (top-level `return` allowed) or an expression; one
14
+ * that parses as neither is read as far as the parser recovers.
15
+ */
16
+ export declare function metadataReads(code: string): string[];
17
+ /** The keys `code` reads that the engine does not set, each once, in order of first read. */
18
+ export declare function unknownMetadataKeys(code: string): string[];
@@ -0,0 +1,63 @@
1
+ import { typescript } from './typescript-module.js';
2
+ import { isReference, isUndefinedSymbol, oneFileChecker } from './script-function.js';
3
+ /** The keys the engine sets on every instance's `$metadata`. */
4
+ export const FLOW_METADATA_KEYS = ['FolderKey', 'InstanceId', 'ProcessKey', 'ExternalId', 'ElementCount'];
5
+ const METADATA = '$metadata';
6
+ /**
7
+ * Every `$metadata` key `code` reads by name, in source order: a property
8
+ * access (`$metadata.InstanceId`, `?.`, with any whitespace or comment around
9
+ * the dot) or a string-literal element access (`$metadata['InstanceId']`).
10
+ *
11
+ * Read from the parsed code, with names resolved by a one-file checker, so only
12
+ * the runtime's own global counts: a local or parameter named `$metadata`, a
13
+ * member of that name (`o.$metadata.Region`), and text in a comment or string
14
+ * are not reads. A computed key reads nothing knowable and is skipped.
15
+ *
16
+ * `code` is a script body (top-level `return` allowed) or an expression; one
17
+ * that parses as neither is read as far as the parser recovers.
18
+ */
19
+ export function metadataReads(code) {
20
+ if (!code.includes(METADATA))
21
+ return [];
22
+ const ts = typescript();
23
+ const sf = parseBodyOrExpression(ts, code);
24
+ const checker = oneFileChecker(ts, sf);
25
+ const out = [];
26
+ const visit = (n) => {
27
+ if (ts.isIdentifier(n) && n.text === METADATA && isReference(ts, n)) {
28
+ const symbol = checker.getSymbolAtLocation(n);
29
+ const key = symbol && !isUndefinedSymbol(symbol) ? undefined : keyRead(ts, n);
30
+ if (key !== undefined)
31
+ out.push(key);
32
+ }
33
+ ts.forEachChild(n, visit);
34
+ };
35
+ visit(sf);
36
+ return out;
37
+ }
38
+ /** The keys `code` reads that the engine does not set, each once, in order of first read. */
39
+ export function unknownMetadataKeys(code) {
40
+ return [...new Set(metadataReads(code).filter((k) => !FLOW_METADATA_KEYS.includes(k)))];
41
+ }
42
+ /** The key `id` (the global) is read with: `.key`, `?.key`, or `['key']`. */
43
+ function keyRead(ts, id) {
44
+ const p = id.parent;
45
+ if (ts.isPropertyAccessExpression(p) && p.expression === id && ts.isIdentifier(p.name))
46
+ return p.name.text;
47
+ if (ts.isElementAccessExpression(p) && p.expression === id && ts.isStringLiteralLike(p.argumentExpression))
48
+ return p.argumentExpression.text;
49
+ return undefined;
50
+ }
51
+ /**
52
+ * `code` as a function body, or, when that does not parse and the expression
53
+ * does (a bare `{ a: $metadata.X }` is a block otherwise), as an expression.
54
+ */
55
+ function parseBodyOrExpression(ts, code) {
56
+ const parse = (text) => ts.createSourceFile('metadata-reads.ts', text, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
57
+ const errors = (sf) => (sf.parseDiagnostics ?? []).length;
58
+ const body = parse(`function __body() {\n${code}\n}`);
59
+ if (errors(body) === 0)
60
+ return body;
61
+ const expression = parse(`(${code}\n)`);
62
+ return errors(expression) === 0 ? expression : body;
63
+ }
@@ -683,6 +683,7 @@ export declare const REFUSALS: {
683
683
  readonly UNKNOWN_ESCALATION_REF: "lowering";
684
684
  readonly UNKNOWN_EXIT_STAGE: "lowering";
685
685
  readonly UNKNOWN_LOOP_FIELD: "lint";
686
+ readonly UNKNOWN_METADATA_KEY: "lint";
686
687
  readonly UNKNOWN_REF: "lint";
687
688
  readonly UNKNOWN_RULE: "lowering";
688
689
  readonly UNKNOWN_SLA_REF: "lowering";
@@ -735,6 +735,7 @@ export const REFUSALS = {
735
735
  UNKNOWN_ESCALATION_REF: 'lowering',
736
736
  UNKNOWN_EXIT_STAGE: 'lowering',
737
737
  UNKNOWN_LOOP_FIELD: 'lint',
738
+ UNKNOWN_METADATA_KEY: 'lint',
738
739
  UNKNOWN_REF: 'lint',
739
740
  UNKNOWN_RULE: 'lowering',
740
741
  UNKNOWN_SLA_REF: 'lowering',
@@ -10,27 +10,46 @@
10
10
  * artifact goes to a designer, and the author expects to see the script they
11
11
  * wrote. Only two edits are made, both because the runtime requires them:
12
12
  *
13
- * 1. the parameter is renamed to the runtime's own scope global (`$vars` in
14
- * Flow). The parameter is an authoring-time name for that global; the SDK
15
- * never calls the function or passes it anything.
13
+ * 1. each parameter is renamed to the runtime's own global for its scope
14
+ * (`$vars`, then `$metadata`, in Flow). A parameter is an authoring-time
15
+ * name for that global; the SDK never calls the function or passes it
16
+ * anything.
16
17
  * 2. esbuild's `keepNames` wrappers (`__name(fn, "fn")`) are removed. A loader
17
18
  * such as tsx adds them, and the script runtime has no `__name`.
18
19
  *
19
20
  * Everything else that would make the lowered body behave differently from the
20
21
  * function is refused rather than rewritten: a name the runtime does not
21
- * provide, a `$vars` binding that would capture the rename, a destructured
22
- * parameter whose step reads `renameRefs` could not see, a reference to the
23
- * function's own name, which only the body loses.
22
+ * provide, a `$vars` (or `$metadata`) binding that would capture the rename, a
23
+ * destructured parameter whose step reads `renameRefs` could not see, a
24
+ * reference to the function's own name, which only the body loses.
24
25
  *
25
26
  * Names are resolved with the checker of a one-file program over the function's
26
27
  * source, not by text: `a.vars`, `{ vars: 1 }` and an inner `(vars) => …` are
27
28
  * not references to the parameter, and only a checker knows that.
28
29
  */
29
30
  import type * as TS from 'typescript';
30
- /** How a runtime receives a lowered script: its scope global, and every other name it provides. */
31
+ /** One scope a runtime binds to a global, which a script function takes as a parameter. */
32
+ export interface ScriptScope {
33
+ /** The identifier the runtime binds it to: `$vars`. */
34
+ global: string;
35
+ /** The plain parameter name errors suggest for it: `vars`. */
36
+ param: string;
37
+ /** What it holds, as errors name it: `the variable scope`. */
38
+ holds: string;
39
+ /** What a dotted read of it names, as errors name it: `step` (`$vars.<step>`). */
40
+ reads: string;
41
+ /** A dotted read's tail, as errors show it: `start.output.x`. */
42
+ example: string;
43
+ /** What sees only a dotted read, as errors name it: `the step renaming and reference checks`. */
44
+ checkedBy: string;
45
+ }
46
+ /** How a runtime receives a lowered script: its scope globals, and every other name it provides. */
31
47
  export interface ScriptRuntime {
32
- /** The identifier the runtime binds the variable scope to (`$vars` in Flow). */
33
- scopeGlobal: string;
48
+ /**
49
+ * The scopes the runtime binds, in parameter order (`$vars`, then `$metadata`,
50
+ * in Flow): a script function takes one parameter per scope, or fewer.
51
+ */
52
+ scopes: readonly ScriptScope[];
34
53
  /** Every other global name the runtime provides: the language built-ins it has, `console`. */
35
54
  globals: ReadonlySet<string>;
36
55
  /**
@@ -75,19 +94,30 @@ export declare function scriptFunctionLowerings(): readonly string[];
75
94
  /** Options for {@link tryLowerScriptFunction}. */
76
95
  export interface LowerOptions {
77
96
  /**
78
- * Refuse any use of the parameter other than a dotted read (`vars.start…`):
97
+ * Refuse any use of a parameter other than a dotted read (`vars.start…`):
79
98
  * an alias (`const v = vars`), a computed read (`vars['start']`), an optional
80
99
  * read (`vars?.start`), or passing it on (`pick(vars)`, `{ vars }`) each hides
81
100
  * the step it reads from `renameRefs` and the reference check, which see only
82
- * `$vars.<name>`. Authoring (`script(fn)`) sets it; decompile's gate does not,
83
- * because there the text is the node's own either way.
101
+ * `$vars.<name>` (and, for `$metadata`, the key it reads from the key check).
102
+ * Authoring (`script(fn)`) sets it; decompile's gate does not, because there
103
+ * the text is the node's own either way.
84
104
  */
85
105
  scopeReadsOnly?: boolean;
86
106
  }
87
107
  /** What lowering a function's source produced: its body, or every problem with it. */
88
- export type LoweredScript = {
108
+ export type LoweredScript =
109
+ /**
110
+ * `scopesRead` is the scope globals whose parameter the body reads, and
111
+ * `scopesDeclared` the ones the body binds in the function's own scope (a
112
+ * top-level `let`, `const`, `class` or `function`, or a `var` outside a nested
113
+ * function), each in scope order. A body that binds one reads its own local,
114
+ * and a parameter of that name printed beside it would clash.
115
+ */
116
+ {
89
117
  ok: true;
90
118
  code: string;
119
+ scopesRead: string[];
120
+ scopesDeclared: string[];
91
121
  }
92
122
  /**
93
123
  * `free` is the names the runtime would not have, also described in `problems`;
@@ -111,18 +141,18 @@ export declare function tryLowerScriptFunction(source: string, runtime: ScriptRu
111
141
  export declare function scriptBodySyntaxError(code: string): string | undefined;
112
142
  /**
113
143
  * The JavaScript engine's early error for a body run as
114
- * `function (<scopeGlobal>) { "use strict"; <code> }`, or undefined when it has
144
+ * `function (<params>) { "use strict"; <code> }`, or undefined when it has
115
145
  * none.
116
146
  *
117
147
  * TypeScript's parser does not report early errors (a duplicate `let`, a
118
- * `const $vars` that clashes with the parameter, an octal literal in strict
148
+ * `const $vars` that clashes with a parameter, an octal literal in strict
119
149
  * code), and the printed function lives in an ES module, which is strict. A body
120
150
  * with one would make the whole module unloadable, so decompile asks the engine.
121
151
  * The body is compiled, never called. A host without the `Function`
122
152
  * constructor (a strict content-security policy) cannot answer, which is
123
153
  * reported as an error too, so the caller keeps the string form.
124
154
  */
125
- export declare function scriptBodyEarlyError(code: string, runtime: ScriptRuntime): string | undefined;
155
+ export declare function scriptBodyEarlyError(code: string, params: readonly string[]): string | undefined;
126
156
  /**
127
157
  * The first piece of TypeScript-only syntax in a body, described, or undefined.
128
158
  *
@@ -134,5 +164,9 @@ export declare function scriptBodyEarlyError(code: string, runtime: ScriptRuntim
134
164
  * string form.
135
165
  */
136
166
  export declare function scriptBodyTypeScriptSyntax(code: string): string | undefined;
167
+ /** The checker of a one-file program over `sf`, with no default library: globals resolve to nothing. */
168
+ export declare function oneFileChecker(ts: typeof TS, sf: TS.SourceFile): TS.TypeChecker;
169
+ /** The checker's own symbol for `undefined`, which a no-lib program still binds. */
170
+ export declare function isUndefinedSymbol(s: TS.Symbol): boolean;
137
171
  /** Whether `id` is read as a value, not a property name, label, or type. */
138
172
  export declare function isReference(ts: typeof TS, id: TS.Identifier): boolean;
@@ -77,18 +77,18 @@ export function scriptBodySyntaxError(code) {
77
77
  }
78
78
  /**
79
79
  * The JavaScript engine's early error for a body run as
80
- * `function (<scopeGlobal>) { "use strict"; <code> }`, or undefined when it has
80
+ * `function (<params>) { "use strict"; <code> }`, or undefined when it has
81
81
  * none.
82
82
  *
83
83
  * TypeScript's parser does not report early errors (a duplicate `let`, a
84
- * `const $vars` that clashes with the parameter, an octal literal in strict
84
+ * `const $vars` that clashes with a parameter, an octal literal in strict
85
85
  * code), and the printed function lives in an ES module, which is strict. A body
86
86
  * with one would make the whole module unloadable, so decompile asks the engine.
87
87
  * The body is compiled, never called. A host without the `Function`
88
88
  * constructor (a strict content-security policy) cannot answer, which is
89
89
  * reported as an error too, so the caller keeps the string form.
90
90
  */
91
- export function scriptBodyEarlyError(code, runtime) {
91
+ export function scriptBodyEarlyError(code, params) {
92
92
  // Compiled for their early errors only; neither is invoked. Module code also
93
93
  // reserves `await`, which no synchronous constructor reports: an async
94
94
  // function reserves it the same way, so a body must compile as both. (A
@@ -96,8 +96,8 @@ export function scriptBodyEarlyError(code, runtime) {
96
96
  // the module's plain function.)
97
97
  const AsyncFunction = (async () => { }).constructor;
98
98
  try {
99
- new Function(runtime.scopeGlobal, `"use strict";\n${code}`);
100
- new AsyncFunction(runtime.scopeGlobal, `"use strict";\n${code}`);
99
+ new Function(...params, `"use strict";\n${code}`);
100
+ new AsyncFunction(...params, `"use strict";\n${code}`);
101
101
  return undefined;
102
102
  }
103
103
  catch (e) {
@@ -167,6 +167,7 @@ function functionName(fn) {
167
167
  return fn.name || undefined;
168
168
  return /^\s*(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/.exec(fn)?.[1];
169
169
  }
170
+ const ORDINALS = ['first', 'second', 'third', 'fourth'];
170
171
  class Lowering {
171
172
  ts;
172
173
  sf;
@@ -177,9 +178,9 @@ class Lowering {
177
178
  problems = [];
178
179
  edits = [];
179
180
  free = new Set();
180
- aliased = new Set();
181
- param;
182
- paramSymbol;
181
+ aliased = new Map();
182
+ read = new Set();
183
+ params = [];
183
184
  ownNameSymbol;
184
185
  constructor(ts, sf, fn, runtime, options) {
185
186
  this.ts = ts;
@@ -196,18 +197,18 @@ class Lowering {
196
197
  return { ok: false, problems: this.problems, free: [] };
197
198
  if (ts.isFunctionExpression(fn) && fn.name)
198
199
  this.ownNameSymbol = this.checker.getSymbolAtLocation(fn.name);
199
- const renaming = this.param !== undefined && this.param.text !== this.runtime.scopeGlobal;
200
- if (renaming) {
201
- this.checkNoCapture();
202
- this.checkNoRedeclaredParameter();
200
+ for (const param of this.params) {
201
+ if (param.id.text === param.scope.global)
202
+ continue;
203
+ this.checkNoCapture(param.scope.global);
204
+ this.checkNoRedeclaredParameter(param);
203
205
  }
204
206
  this.checkConstGlobals();
205
207
  this.visit(fn.body, 0);
206
- if (this.aliased.size > 0) {
207
- const name = [...this.aliased][0];
208
- this.problems.push(`it uses its parameter \`${name}\` other than as a dotted read (\`${name}.start.output.x\`): an alias, a computed or optional read, `
209
- + `or passing \`${name}\` on hides the step it reads from the step renaming and reference checks, which see only \`${this.runtime.scopeGlobal}.<step>\`. `
210
- + 'Read each step with dots.');
208
+ for (const [name, scope] of this.aliased) {
209
+ this.problems.push(`it uses its parameter \`${name}\` other than as a dotted read (\`${name}.${scope.example}\`): an alias, a computed or optional read, `
210
+ + `or passing \`${name}\` on hides the ${scope.reads} it reads from ${scope.checkedBy}, which see only \`${scope.global}.<${scope.reads}>\`. `
211
+ + `Read each ${scope.reads} with dots.`);
211
212
  }
212
213
  const free = [...this.free].sort();
213
214
  if (free.length > 0) {
@@ -216,7 +217,9 @@ class Lowering {
216
217
  }
217
218
  if (this.problems.length > 0)
218
219
  return { ok: false, problems: this.problems, free };
219
- return { ok: true, code: this.body() };
220
+ const scopesRead = this.runtime.scopes.filter((s) => this.read.has(s)).map((s) => s.global);
221
+ const scopesDeclared = this.runtime.scopes.filter((s) => this.bindsInFunctionScope(s.global)).map((s) => s.global);
222
+ return { ok: true, code: this.body(), scopesRead, scopesDeclared };
220
223
  }
221
224
  /** The function forms that cannot become a body at all. */
222
225
  checkShape() {
@@ -226,34 +229,49 @@ class Lowering {
226
229
  this.problems.push('it is `async`; a script body runs synchronously.');
227
230
  if (ts.isFunctionExpression(fn) && fn.asteriskToken)
228
231
  this.problems.push('it is a generator; a script body returns one value.');
229
- if (fn.parameters.length > 1) {
230
- this.problems.push(`it takes ${fn.parameters.length} parameters; a script function takes one, the runtime's variable scope (${this.runtime.scopeGlobal}), or none.`);
231
- return;
232
- }
233
- const p = fn.parameters[0];
234
- if (!p)
232
+ const { scopes } = this.runtime;
233
+ if (fn.parameters.length > scopes.length) {
234
+ const order = scopes.map((s) => `${s.holds} (${s.global})`);
235
+ this.problems.push(scopes.length === 1
236
+ ? `it takes ${fn.parameters.length} parameters; a script function takes one, ${order[0]}, or none.`
237
+ : `it takes ${fn.parameters.length} parameters; a script function takes at most ${scopes.length}, in this order: ${order.join(', ')}.`);
235
238
  return;
236
- if (p.dotDotDotToken)
237
- this.problems.push('its parameter is a rest parameter; take the variable scope as one plain parameter.');
238
- else if (p.initializer)
239
- this.problems.push('its parameter has a default value; the runtime always supplies the variable scope.');
240
- else if (!ts.isIdentifier(p.name)) {
241
- this.problems.push('its parameter is destructured. Take the variable scope as one plain parameter and read it with dots '
242
- + `(\`vars.start.output.x\`): a destructured read is invisible to the step renaming and reference checks that read \`${this.runtime.scopeGlobal}.<step>\`.`);
243
- }
244
- else {
245
- this.param = p.name;
246
- this.paramSymbol = this.checker.getSymbolAtLocation(p.name);
247
239
  }
240
+ fn.parameters.forEach((p, i) => {
241
+ const scope = scopes[i];
242
+ const its = fn.parameters.length > 1 ? `its ${ORDINALS[i]} parameter` : 'its parameter';
243
+ if (p.dotDotDotToken)
244
+ this.problems.push(`${its} is a rest parameter; take ${scope.holds} as one plain parameter.`);
245
+ else if (p.initializer)
246
+ this.problems.push(`${its} has a default value; the runtime always supplies ${scope.holds}.`);
247
+ else if (!ts.isIdentifier(p.name)) {
248
+ this.problems.push(`${its} is destructured. Take ${scope.holds} as one plain parameter and read it with dots `
249
+ + `(\`${scope.param}.${scope.example}\`): a destructured read is invisible to ${scope.checkedBy} that read \`${scope.global}.<${scope.reads}>\`.`);
250
+ }
251
+ else {
252
+ // Parameters bind by position, so one named like another scope would be
253
+ // renamed to its own scope's global and read that scope instead:
254
+ // `($metadata) => $metadata.InstanceId` would read `$vars.InstanceId`.
255
+ const name = p.name.text;
256
+ const named = scopes.find((other) => other !== scope && other.global === name);
257
+ if (named) {
258
+ this.problems.push(`${its} is named \`${named.global}\` but receives ${scope.holds} (${scope.global}): parameters bind by position, `
259
+ + `and \`${named.global}\` is the ${ORDINALS[scopes.indexOf(named)]} one. `
260
+ + `Take them in order, \`(${scopes.map((s) => s.global).join(', ')}) => …\`, or give this one another name.`);
261
+ }
262
+ else {
263
+ this.params.push({ id: p.name, symbol: this.checker.getSymbolAtLocation(p.name), scope });
264
+ }
265
+ }
266
+ });
248
267
  }
249
268
  /**
250
- * Refuse a binding named like the scope global anywhere in the function: once
251
- * the parameter's references are rewritten to that name, the binding would
269
+ * Refuse a binding named like a scope global anywhere in the function: once
270
+ * a parameter's references are rewritten to that name, the binding would
252
271
  * capture them (`const $vars = {…}; return vars.x` would read the local).
253
272
  */
254
- checkNoCapture() {
273
+ checkNoCapture(target) {
255
274
  const { ts } = this;
256
- const target = this.runtime.scopeGlobal;
257
275
  let found = false;
258
276
  const visit = (n) => {
259
277
  if (found)
@@ -269,6 +287,38 @@ class Lowering {
269
287
  + `Rename the inner binding, or name the parameter \`${target}\` so nothing is renamed.`);
270
288
  }
271
289
  }
290
+ /**
291
+ * Whether the body binds `name` in the function's own scope: a top-level
292
+ * `let`, `const`, `class` or `function` (a label does not scope what it
293
+ * labels), or a `var` anywhere outside a nested function, which hoists.
294
+ */
295
+ bindsInFunctionScope(name) {
296
+ const { ts } = this;
297
+ const body = this.fn.body;
298
+ if (!ts.isBlock(body))
299
+ return false;
300
+ for (let stmt of body.statements) {
301
+ while (ts.isLabeledStatement(stmt))
302
+ stmt = stmt.statement;
303
+ if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.some((d) => bindsName(ts, d.name, name)))
304
+ return true;
305
+ if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name?.text === name)
306
+ return true;
307
+ }
308
+ let found = false;
309
+ const vars = (n) => {
310
+ if (found || ts.isFunctionLike(n) || ts.isClassLike(n))
311
+ return;
312
+ if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0
313
+ && n.declarations.some((d) => bindsName(ts, d.name, name))) {
314
+ found = true;
315
+ return;
316
+ }
317
+ ts.forEachChild(n, vars);
318
+ };
319
+ ts.forEachChild(body, vars);
320
+ return found;
321
+ }
272
322
  /**
273
323
  * Refuse a `var vars` or `function vars() {}` in the body: either binds the
274
324
  * parameter's own name in the function's scope, so the rename would rewrite the
@@ -278,9 +328,9 @@ class Lowering {
278
328
  * still resolving the reads to the parameter. A nested function (arrows
279
329
  * included) has its own `var` scope, so its declarations are its own.
280
330
  */
281
- checkNoRedeclaredParameter() {
331
+ checkNoRedeclaredParameter(param) {
282
332
  const { ts } = this;
283
- const name = this.param.text;
333
+ const name = param.id.text;
284
334
  let found = false;
285
335
  const visit = (n) => {
286
336
  if (found)
@@ -303,7 +353,7 @@ class Lowering {
303
353
  };
304
354
  visit(this.fn.body);
305
355
  if (found) {
306
- this.problems.push(`it redeclares its parameter \`${name}\` (a \`var\` or a function declaration), which the rename to \`${this.runtime.scopeGlobal}\` would split from its reads. `
356
+ this.problems.push(`it redeclares its parameter \`${name}\` (a \`var\` or a function declaration), which the rename to \`${param.scope.global}\` would split from its reads. `
307
357
  + 'Give the local a name of its own.');
308
358
  }
309
359
  }
@@ -445,14 +495,18 @@ class Lowering {
445
495
  const parent = id.parent;
446
496
  const shorthand = ts.isShorthandPropertyAssignment(parent) && parent.name === id;
447
497
  const symbol = shorthand ? this.checker.getShorthandAssignmentValueSymbol(parent) : this.checker.getSymbolAtLocation(id);
448
- if (symbol && symbol === this.paramSymbol) {
498
+ const param = symbol ? this.params.find((p) => p.symbol === symbol) : undefined;
499
+ if (param) {
500
+ const { scope } = param;
501
+ this.read.add(scope);
449
502
  if (this.options.scopeReadsOnly && !(ts.isPropertyAccessExpression(parent) && parent.expression === id && !parent.questionDotToken)) {
450
- this.aliased.add(id.text);
503
+ if (!this.aliased.has(id.text))
504
+ this.aliased.set(id.text, scope);
451
505
  }
452
- if (this.param.text !== this.runtime.scopeGlobal) {
506
+ if (param.id.text !== scope.global) {
453
507
  this.edits.push(shorthand
454
- ? [id.getStart(), id.end, `${id.text}: ${this.runtime.scopeGlobal}`]
455
- : [id.getStart(), id.end, this.runtime.scopeGlobal]);
508
+ ? [id.getStart(), id.end, `${id.text}: ${scope.global}`]
509
+ : [id.getStart(), id.end, scope.global]);
456
510
  }
457
511
  return;
458
512
  }
@@ -471,7 +525,7 @@ class Lowering {
471
525
  }
472
526
  if (symbol && !isUndefinedSymbol(symbol))
473
527
  return;
474
- if (id.text === this.runtime.scopeGlobal || this.runtime.globals.has(id.text)) {
528
+ if (this.runtime.scopes.some((s) => s.global === id.text) || this.runtime.globals.has(id.text)) {
475
529
  this.checkGlobalMember(id);
476
530
  return;
477
531
  }
@@ -541,7 +595,7 @@ class Lowering {
541
595
  }
542
596
  }
543
597
  /** The checker of a one-file program over `sf`, with no default library: globals resolve to nothing. */
544
- function oneFileChecker(ts, sf) {
598
+ export function oneFileChecker(ts, sf) {
545
599
  const options = { noLib: true, noResolve: true, types: [], target: ts.ScriptTarget.ES2020 };
546
600
  const host = {
547
601
  getSourceFile: (name) => (name === sf.fileName ? sf : undefined),
@@ -557,7 +611,7 @@ function oneFileChecker(ts, sf) {
557
611
  return ts.createProgram({ rootNames: [sf.fileName], options, host }).getTypeChecker();
558
612
  }
559
613
  /** The checker's own symbol for `undefined`, which a no-lib program still binds. */
560
- function isUndefinedSymbol(s) {
614
+ export function isUndefinedSymbol(s) {
561
615
  return s.escapedName === 'undefined' && (s.declarations?.length ?? 0) === 0;
562
616
  }
563
617
  /** Whether `id` is read as a value, not a property name, label, or type. */
@@ -9,9 +9,11 @@ import type { ScriptInputs, ScriptValue } from './spec.js';
9
9
  * Declare an inline script action, as a TypeScript function the compiler checks.
10
10
  *
11
11
  * The function is never called. Its body, exactly as written, becomes the
12
- * script; its parameter is an authoring-time name for the Flow runtime's own
13
- * `$vars` global and is renamed to it. `returns` is the declaration that reaches
14
- * the `.flow`, and tsc checks the function's return against it.
12
+ * script; its parameters are authoring-time names for the Flow runtime's own
13
+ * `$vars` and `$metadata` globals (typed `FlowVars` and `FlowMetadata`), in
14
+ * that order, and are renamed to them. Take neither, `$vars` alone, or both.
15
+ * `returns` is the declaration that reaches the `.flow`, and tsc checks the
16
+ * function's return against it.
15
17
  *
16
18
  * The function may read only what the script runtime has (the ES2020 built-ins
17
19
  * and `console`): a module constant, an import or a library throws here, because
@@ -20,29 +22,29 @@ import type { ScriptInputs, ScriptValue } from './spec.js';
20
22
  * ```ts
21
23
  * .step('post', script(parseMessage, { returns: { channel: 'string', threadTs: 'string' } }))
22
24
  *
23
- * function parseMessage(vars: FlowVars) {
24
- * const m = /archives\/([A-Z0-9]+)\/p(\d{10})(\d{6})/.exec(String(vars.start.output.message));
25
+ * function parseMessage($vars: FlowVars) {
26
+ * const m = /archives\/([A-Z0-9]+)\/p(\d{10})(\d{6})/.exec(String($vars.start.output.message));
25
27
  * if (!m) throw new Error('not a Slack permalink');
26
28
  * return { channel: m[1], threadTs: m[2] + '.' + m[3] };
27
29
  * }
28
30
  * ```
29
31
  *
30
- * @param fn - The script, as a function of the variable scope (or of nothing).
32
+ * @param fn - The script, as a function of the variable scope and the instance metadata (or of fewer).
31
33
  * @param options - What the function returns (`returns`), as `script({ returns })` takes it.
32
34
  * @returns An action spec to pass to `.step(name, …)`.
33
35
  */
34
- export declare function script<V, const R extends ScriptReturns>(fn: (vars: V) => ScriptValue<R>, options: {
36
+ export declare function script<V, const R extends ScriptReturns, M = unknown>(fn: (vars: V, metadata: M) => ScriptValue<R>, options: {
35
37
  returns: R;
36
38
  }): ActionSpec;
37
39
  /**
38
40
  * Declare an inline script action, as a TypeScript function, with no declared
39
41
  * return: the body's type is inferred as it is for a string body.
40
42
  *
41
- * @param fn - The script, as a function of the variable scope (or of nothing).
43
+ * @param fn - The script, as a function of the variable scope and the instance metadata (or of fewer).
42
44
  * @param options - No `returns`; pass one to have tsc check the function against it.
43
45
  * @returns An action spec to pass to `.step(name, …)`.
44
46
  */
45
- export declare function script<V>(fn: (vars: V) => unknown, options?: {
47
+ export declare function script<V, M = unknown>(fn: (vars: V, metadata: M) => unknown, options?: {
46
48
  returns?: never;
47
49
  }): ActionSpec;
48
50
  /**
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { declaredScriptShape, inferScriptShape } from '../../../core/script-return.js';
8
8
  import { jsonClone } from '../../../core/json-clone.js';
9
+ import { unknownMetadataKeys } from '../../../core/flow-metadata.js';
9
10
  import { scriptBodyEarlyError, scriptBodySyntaxError, scriptBodyTypeScriptSyntax, tryLowerScriptFunction } from '../../../core/script-function.js';
10
11
  import { FLOW_SCRIPT_RUNTIME } from './runtime.js';
11
12
  import { checkScript } from './check.js';
@@ -106,12 +107,15 @@ const UNCHANGED = 'its text would not come back unchanged as a function body (a
106
107
  * this holds for nearly every body that passes the others; it is checked
107
108
  * because it is cheap, and a line comment on the last line is the case that
108
109
  * fails it (it would swallow the closing brace);
109
- * 5. the engine compiles it as a strict function of `$vars`: an early error
110
- * (a duplicate `let`, a `const $vars`, an octal literal) that TypeScript's
111
- * parser does not report would make the whole module unloadable.
110
+ * 5. the engine compiles it as a strict function of its parameters: an early
111
+ * error (a duplicate `let`, a `const $vars`, an octal literal) that
112
+ * TypeScript's parser does not report would make the whole module
113
+ * unloadable.
112
114
  *
113
- * The parameter is named `$vars`, so nothing is renamed and the body's text is
114
- * the node's text.
115
+ * The parameters are named `$vars` and, when the body reads it, `$metadata`, so
116
+ * nothing is renamed and the body's text is the node's text. A body that binds
117
+ * its own `$metadata` in the function's scope reads that, and prints without
118
+ * the parameter, which would clash with it.
115
119
  */
116
120
  function printAsFunction(code, ctx) {
117
121
  if (ctx.scripts !== 'function')
@@ -130,7 +134,7 @@ function printAsFunction(code, ctx) {
130
134
  // With authoring's own rules: the printed function is lowered by `script(fn)`
131
135
  // when the source is compiled back, so a body that rule refuses (`$vars?.x`,
132
136
  // `$vars['x']`, an alias) must stay a string here, or compile would refuse it.
133
- const lowered = tryLowerScriptFunction(`function ${name}($vars) {${code}}`, FLOW_SCRIPT_RUNTIME, { scopeReadsOnly: true });
137
+ const lowered = tryLowerScriptFunction(`function ${name}($vars, $metadata) {${code}}`, FLOW_SCRIPT_RUNTIME, { scopeReadsOnly: true });
134
138
  if (!lowered.ok) {
135
139
  // Worded as the SDK's list, not as a fault: whether a string body that reads
136
140
  // the name really fails in the run is what #1411 has yet to establish.
@@ -144,11 +148,21 @@ function printAsFunction(code, ctx) {
144
148
  }
145
149
  if (lowered.code !== code)
146
150
  return keep(UNCHANGED);
151
+ // A body that binds its own `$metadata` in the function's scope (`const`, or a
152
+ // `var`, which is no early error but would not type-check beside a
153
+ // `FlowMetadata` parameter) reads that local, so it prints without one.
154
+ const metadata = lowered.scopesRead.includes('$metadata') && !lowered.scopesDeclared.includes('$metadata');
147
155
  // Last, on what will actually be printed: an early error makes the module unloadable.
148
- const early = scriptBodyEarlyError(code, FLOW_SCRIPT_RUNTIME);
156
+ const early = scriptBodyEarlyError(code, metadata ? ['$vars', '$metadata'] : ['$vars']);
149
157
  if (early)
150
158
  return keep(`as a function in an ES module (strict code) it would not load: ${early}.`);
151
- ctx.declareFunction(`function ${name}($vars: ${ctx.typeHelper('FlowVars')}) {${code}}`);
159
+ // A key the engine does not set (a case may set it) is not on `FlowMetadata`,
160
+ // so the type is widened for it, or the printed module would not type-check.
161
+ const metadataType = () => (unknownMetadataKeys(code).length > 0
162
+ ? `${ctx.typeHelper('FlowMetadata')} & Record<string, any>`
163
+ : ctx.typeHelper('FlowMetadata'));
164
+ const params = [`$vars: ${ctx.typeHelper('FlowVars')}`, ...(metadata ? [`$metadata: ${metadataType()}`] : [])];
165
+ ctx.declareFunction(`function ${name}(${params.join(', ')}) {${code}}`);
152
166
  return name;
153
167
  }
154
168
  /** Key-sorted copy, so a property map compares by content and not by order. */
@@ -5,7 +5,14 @@
5
5
  import type { ScriptRuntime } from '../../../core/script-function.js';
6
6
  /**
7
7
  * What a Flow script body can read: the language's ES2020 built-ins, the
8
- * globals the engine registers, `console`, and the runtime's `$vars`.
8
+ * globals the engine registers, `console`, and the runtime's `$vars` and
9
+ * `$metadata`.
10
+ *
11
+ * The two scopes are the script task's two base inputs: Flow's converter gives
12
+ * every script task `vars = "=vars"` and `metadata = "=metadata"`, and rewrites
13
+ * `$vars`/`$metadata` in the body to those names (UiPath/flow-workbench,
14
+ * `bpmn-to-xml.ts` and `expression-transform.ts`); the engine binds each input
15
+ * as a global. A script function takes them as its parameters, in that order.
9
16
  *
10
17
  * Measured against UiPath/PO.BpmnEngine, which runs a script body with
11
18
  * Jint 4.4.0 (`ScriptTaskService.InvokeScriptAsync`,
@@ -20,7 +20,14 @@ const FLOW_SCRIPT_GLOBAL_MEMBERS = new Map([
20
20
  ]);
21
21
  /**
22
22
  * What a Flow script body can read: the language's ES2020 built-ins, the
23
- * globals the engine registers, `console`, and the runtime's `$vars`.
23
+ * globals the engine registers, `console`, and the runtime's `$vars` and
24
+ * `$metadata`.
25
+ *
26
+ * The two scopes are the script task's two base inputs: Flow's converter gives
27
+ * every script task `vars = "=vars"` and `metadata = "=metadata"`, and rewrites
28
+ * `$vars`/`$metadata` in the body to those names (UiPath/flow-workbench,
29
+ * `bpmn-to-xml.ts` and `expression-transform.ts`); the engine binds each input
30
+ * as a global. A script function takes them as its parameters, in that order.
24
31
  *
25
32
  * Measured against UiPath/PO.BpmnEngine, which runs a script body with
26
33
  * Jint 4.4.0 (`ScriptTaskService.InvokeScriptAsync`,
@@ -45,7 +52,16 @@ const FLOW_SCRIPT_GLOBAL_MEMBERS = new Map([
45
52
  * there is evidence the list is complete (#1411).
46
53
  */
47
54
  export const FLOW_SCRIPT_RUNTIME = {
48
- scopeGlobal: '$vars',
55
+ scopes: [
56
+ {
57
+ global: '$vars', param: 'vars', holds: 'the variable scope', reads: 'step', example: 'start.output.x',
58
+ checkedBy: 'the step renaming and reference checks',
59
+ },
60
+ {
61
+ global: '$metadata', param: 'metadata', holds: 'the instance metadata', reads: 'key', example: 'InstanceId',
62
+ checkedBy: 'the metadata key check',
63
+ },
64
+ ],
49
65
  name: 'the Flow script runtime',
50
66
  constGlobals: FLOW_SCRIPT_CONST_GLOBALS,
51
67
  globalMembers: FLOW_SCRIPT_GLOBAL_MEMBERS,
@@ -60,6 +60,39 @@ export interface ScriptInputs {
60
60
  * ```
61
61
  */
62
62
  export type FlowVars = Record<string, any>;
63
+ /**
64
+ * The instance metadata, as a script function's second parameter sees it.
65
+ *
66
+ * Like `$vars`, `$metadata` is the Flow runtime's own global: `script(fn)`
67
+ * renames the second parameter to it. Unlike `$vars` its shape does not depend
68
+ * on the flow: these are the keys the engine sets on every instance
69
+ * (UiPath/PO.BpmnEngine, `BpmnGeneric.workflow.cs` `MapToMetadataScopeData`, and
70
+ * `BpmnDataManager.UpsertElementCountDatum`). `check` warns on a read of any
71
+ * other key (`UNKNOWN_METADATA_KEY`).
72
+ *
73
+ * A case that starts the flow may set more. To read one of those, widen the
74
+ * parameter's type (`$metadata: FlowMetadata & { Region?: string }`).
75
+ *
76
+ * ```ts
77
+ * .step('tag', script(tag, { returns: 'string' }))
78
+ *
79
+ * function tag($vars: FlowVars, $metadata: FlowMetadata) {
80
+ * return $metadata.InstanceId + ':' + $vars.start.output.id;
81
+ * }
82
+ * ```
83
+ */
84
+ export interface FlowMetadata {
85
+ /** The key (UUID) of the Orchestrator folder the instance runs in. */
86
+ FolderKey: string;
87
+ /** The instance's id (UUID). */
88
+ InstanceId: string;
89
+ /** The key of the process the instance runs. */
90
+ ProcessKey: string;
91
+ /** The external id a case gives the instance; unset for a flow no case started. */
92
+ ExternalId?: string;
93
+ /** How many times each element has run, by element id; unset until the first count. */
94
+ ElementCount?: Record<string, number>;
95
+ }
63
96
  /** The TypeScript type one of {@link ScriptReturnType}'s names stands for. */
64
97
  export type ScriptValueOf<T extends ScriptReturnType> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'array' ? readonly unknown[] : T extends 'object' ? object : unknown;
65
98
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.71.1",
3
+ "version": "6.72.0",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "7cd35a2caf9c3ca33c5f5f8c0caae6577d6dc77d"
103
+ "gitref": "3b667059d0108eba01cb0e82fbb049ae0ad04588"
104
104
  }