@uipath/maestro-builder-sdk 6.49.3 → 6.50.1
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/case/check-cli.js +10 -9
- package/dist/case/compile-cli.d.ts +21 -0
- package/dist/case/compile-cli.js +26 -3
- package/dist/check.d.ts +5 -0
- package/dist/check.js +50 -52
- package/dist/cli-library.js +8 -0
- package/dist/core/binding-messages.d.ts +2 -0
- package/dist/core/binding-messages.js +5 -0
- package/dist/core/bindings.d.ts +42 -3
- package/dist/core/bindings.js +67 -5
- package/dist/core/connector-inputs.d.ts +2 -0
- package/dist/core/connector-inputs.js +2 -0
- package/dist/core/nodes/node-type.d.ts +6 -1
- package/dist/core/nodes/versioned.d.ts +7 -0
- package/dist/core/nodes/versioned.js +11 -0
- package/dist/decompile.js +2 -65
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/nodes/connector/flow/family.d.ts +3 -0
- package/dist/nodes/connector/flow/family.js +8 -0
- package/dist/nodes/connector/flow/node-type.d.ts +25 -0
- package/dist/nodes/connector/flow/node-type.js +294 -0
- package/dist/serialize.d.ts +2 -3
- package/dist/serialize.js +46 -182
- package/package.json +2 -2
package/dist/case/check-cli.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* check-cli — fast static validation of <Name>.case.ts (no output file).
|
|
4
4
|
*
|
|
5
5
|
* Usage: node --experimental-strip-types check-cli.js <case.ts | BaseName> \
|
|
6
|
-
* [--library <library-json dir>] [--connectors-local <overlay dir>] [--bindings <bindings.json>]
|
|
6
|
+
* [--library <library-json dir>] [--connectors-local <overlay dir>] [--bindings <bindings.json>] \
|
|
7
|
+
* [-o <the plan compile writes>]
|
|
7
8
|
* Exits 0 when clean (warnings allowed), 1 on any error.
|
|
8
9
|
*
|
|
9
10
|
* The library is opened exactly as `compile-cli` opens it (`openCliLibrary`), so
|
|
@@ -14,19 +15,18 @@ import { resolveCaseFile, loadBuiltCase } from './load.js';
|
|
|
14
15
|
import { check } from './check.js';
|
|
15
16
|
import { caseCheckOpts } from './cli-library.js';
|
|
16
17
|
import { openCliLibrary } from '../core/cli-library.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
18
|
+
import { caseCompileTarget } from './compile-cli.js';
|
|
19
|
+
import { runWhenInvokedDirectly } from '../cli-run.js';
|
|
20
20
|
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
21
21
|
/** Flags that take a value: their argument is never the source positional. */
|
|
22
|
-
const VALUE_FLAGS = new Set(['--library', '--connectors-local', '--bindings']);
|
|
23
|
-
const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>] [--bindings <file>]';
|
|
22
|
+
const VALUE_FLAGS = new Set(['--library', '--connectors-local', '--bindings', '-o']);
|
|
23
|
+
const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>] [--bindings <file>] [-o <plan>]';
|
|
24
24
|
export async function run(argv) {
|
|
25
25
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
26
26
|
console.error(USAGE);
|
|
27
27
|
return 2;
|
|
28
28
|
}
|
|
29
|
-
const input = argv.find((a, i) => !a.startsWith('
|
|
29
|
+
const input = argv.find((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(argv[i - 1]));
|
|
30
30
|
if (input === undefined) {
|
|
31
31
|
console.error(USAGE);
|
|
32
32
|
return 2;
|
|
@@ -34,8 +34,9 @@ export async function run(argv) {
|
|
|
34
34
|
const file = resolveCaseFile(input);
|
|
35
35
|
ensureTypeScriptRuntime();
|
|
36
36
|
const built = await loadBuiltCase(file);
|
|
37
|
-
// bindings
|
|
38
|
-
|
|
37
|
+
// The bindings `case compile` reads, plan fallback included, for the
|
|
38
|
+
// binding-label rules (#1034): `-o` names the plan as it does for compile.
|
|
39
|
+
const { bindings } = caseCompileTarget(argv);
|
|
39
40
|
const diags = check(built, caseCheckOpts(openCliLibrary(argv), bindings));
|
|
40
41
|
reportDiagnostics(diags);
|
|
41
42
|
for (const d of diags)
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Bindings } from '../core/bindings.js';
|
|
2
3
|
import { type CliExitCode } from '../cli-run.js';
|
|
3
4
|
export declare function run(argv: string[]): Promise<CliExitCode>;
|
|
5
|
+
/**
|
|
6
|
+
* The plan file `case compile` writes, and the bindings it resolves connection
|
|
7
|
+
* and folder labels against. `case check` reads the same pair, so the two agree
|
|
8
|
+
* on which labels are declared.
|
|
9
|
+
*
|
|
10
|
+
* The plan is `-o`, else the plan file the project in the working directory
|
|
11
|
+
* ships, else `caseplan.json`. The bindings are `--bindings` (default: the
|
|
12
|
+
* project's bindings.json), with that plan's own root bindings as a fallback
|
|
13
|
+
* when it exists. Recompiling into an existing plan (the brownfield loop:
|
|
14
|
+
* decompile, edit, compile back over the same file) then resolves the binding
|
|
15
|
+
* ids decompile names a connector's connection and folder by
|
|
16
|
+
* (`connection: "bOC1"`).
|
|
17
|
+
*
|
|
18
|
+
* @param argv - The command's arguments.
|
|
19
|
+
* @returns The output path and the bindings registry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function caseCompileTarget(argv: string[]): {
|
|
22
|
+
out: string;
|
|
23
|
+
bindings: Bindings;
|
|
24
|
+
};
|
|
4
25
|
/**
|
|
5
26
|
* Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
|
|
6
27
|
*
|
package/dist/case/compile-cli.js
CHANGED
|
@@ -23,6 +23,7 @@ import { check } from './check.js';
|
|
|
23
23
|
import { caseCheckOpts } from './cli-library.js';
|
|
24
24
|
import { entryPointIO } from './entry-points.js';
|
|
25
25
|
import { Bindings } from '../core/bindings.js';
|
|
26
|
+
import { offlineBindingLabel } from './ids.js';
|
|
26
27
|
import { openCliLibrary } from '../core/cli-library.js';
|
|
27
28
|
import { defaultBindingsFile } from '../workdir.js';
|
|
28
29
|
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
@@ -54,8 +55,7 @@ export async function run(argv) {
|
|
|
54
55
|
// which is what the three copies of these lines used to each decide for itself.
|
|
55
56
|
// Opened BEFORE the check pass, which reads the same library (#1014).
|
|
56
57
|
const library = openCliLibrary(argv);
|
|
57
|
-
const
|
|
58
|
-
const bindings = new Bindings(bindFile);
|
|
58
|
+
const { out, bindings } = caseCompileTarget(argv);
|
|
59
59
|
const diags = check(built, caseCheckOpts(library, bindings));
|
|
60
60
|
for (const d of diags)
|
|
61
61
|
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
|
|
@@ -66,7 +66,6 @@ export async function run(argv) {
|
|
|
66
66
|
console.error(`compile: ${errors.length} error(s); not emitting.`);
|
|
67
67
|
return 1;
|
|
68
68
|
}
|
|
69
|
-
const out = flagValue(argv, '-o') ?? shippedCasePlanName('.') ?? 'caseplan.json';
|
|
70
69
|
const shadow = shadowedOutputDiagnostic(out);
|
|
71
70
|
if (shadow) {
|
|
72
71
|
console.error(` ✗ [${shadow.code}] ${shadow.message} Fix: ${shadow.suggestion}.`);
|
|
@@ -94,6 +93,30 @@ export async function run(argv) {
|
|
|
94
93
|
reportResult({ path: resolve(out), stages, ...(triggers >= 0 ? { triggers } : {}), ...(resources >= 0 ? { resources } : {}) });
|
|
95
94
|
return 0;
|
|
96
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* The plan file `case compile` writes, and the bindings it resolves connection
|
|
98
|
+
* and folder labels against. `case check` reads the same pair, so the two agree
|
|
99
|
+
* on which labels are declared.
|
|
100
|
+
*
|
|
101
|
+
* The plan is `-o`, else the plan file the project in the working directory
|
|
102
|
+
* ships, else `caseplan.json`. The bindings are `--bindings` (default: the
|
|
103
|
+
* project's bindings.json), with that plan's own root bindings as a fallback
|
|
104
|
+
* when it exists. Recompiling into an existing plan (the brownfield loop:
|
|
105
|
+
* decompile, edit, compile back over the same file) then resolves the binding
|
|
106
|
+
* ids decompile names a connector's connection and folder by
|
|
107
|
+
* (`connection: "bOC1"`).
|
|
108
|
+
*
|
|
109
|
+
* @param argv - The command's arguments.
|
|
110
|
+
* @returns The output path and the bindings registry.
|
|
111
|
+
*/
|
|
112
|
+
export function caseCompileTarget(argv) {
|
|
113
|
+
const out = flagValue(argv, '-o') ?? shippedCasePlanName('.') ?? 'caseplan.json';
|
|
114
|
+
const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
|
|
115
|
+
// A row serialize minted offline (`{ id: 'b…', resourceKey: 'drive' }`) is a
|
|
116
|
+
// placeholder for the label `drive`, not a binding: it must not answer.
|
|
117
|
+
const minted = (row) => offlineBindingLabel(row) !== undefined;
|
|
118
|
+
return { out, bindings: new Bindings(bindFile).withFallback(existsSync(out) ? out : undefined, minted) };
|
|
119
|
+
}
|
|
97
120
|
/**
|
|
98
121
|
* The file names a Case project's plan can have, in the order the packager
|
|
99
122
|
* (`@uipath/packager-tool-case` `CasePlanFileNames`) picks the first one present.
|
package/dist/check.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface ListEnvelopeFields {
|
|
|
29
29
|
export interface CheckOpts extends ConnectorCheckOpts, EventCheckOpts {
|
|
30
30
|
/** Resolve the measured collection envelope for one connector operation. */
|
|
31
31
|
listEnvelope?: (connector: string, operation: string, version?: string, object?: string) => ListEnvelopeFields | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The library's definition for one connector operation, the one compile places:
|
|
34
|
+
* whether it grants an error port is then the same answer in both rungs.
|
|
35
|
+
*/
|
|
36
|
+
connectorDefinition?: (connector: string, operation: string, version?: string, object?: string) => unknown;
|
|
32
37
|
}
|
|
33
38
|
export declare function check(built: BuiltFlow, opts?: CheckOpts): Diagnostic[];
|
|
34
39
|
/**
|
package/dist/check.js
CHANGED
|
@@ -230,7 +230,6 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
230
230
|
// Step name → the read rule for its output, bound to its inputs. Filled from
|
|
231
231
|
// OUTPUT_READ_RULES for every kind whose inputs take a `returns`.
|
|
232
232
|
const outputReaders = new Map();
|
|
233
|
-
const connectorSteps = new Map();
|
|
234
233
|
// Subflow step name → the child flow it runs. A subflow's output is the
|
|
235
234
|
// OBJECT of the child's declared outputs, so a read of it is checked against
|
|
236
235
|
// that child rather than waved through (see SUBFLOW_* below).
|
|
@@ -346,7 +345,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
346
345
|
declared.add(v);
|
|
347
346
|
}
|
|
348
347
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === 'error')
|
|
349
|
-
checkOnError(s, prevPortSource, diags, prevPortSource && checkedSteps.get(prevPortSource.name), carriedLookup([built, ...ancestors]));
|
|
348
|
+
checkOnError(s, prevPortSource, diags, prevPortSource && checkedSteps.get(prevPortSource.name), opts, carriedLookup([built, ...ancestors]));
|
|
350
349
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port.startsWith('branch-')) {
|
|
351
350
|
const want = s.port.slice('branch-'.length);
|
|
352
351
|
const prevSpec = prevAction?.spec;
|
|
@@ -465,12 +464,6 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
465
464
|
else {
|
|
466
465
|
collectActionExprs(s, exprs, errorSource);
|
|
467
466
|
}
|
|
468
|
-
if (s.spec.kind === 'connector') {
|
|
469
|
-
connectorSteps.set(s.name, s.spec);
|
|
470
|
-
checkConnectorLookups(s.name, s.spec, opts, diags);
|
|
471
|
-
checkConnectorSchema(s.name, s.spec, opts, diags);
|
|
472
|
-
checkConnectorContract(s.name, s.spec, opts, diags);
|
|
473
|
-
}
|
|
474
467
|
// A custom FlowAction subclass has no `kind`: the family rules below
|
|
475
468
|
// don't apply, and it reports through its own diagnostics() hook.
|
|
476
469
|
const maybeCustom = s.spec;
|
|
@@ -715,7 +708,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
715
708
|
// handle); it is not a legal source for anything else.
|
|
716
709
|
prevPortSource = s;
|
|
717
710
|
addExpr(s.collection.js, s.name);
|
|
718
|
-
checkLoopListEnvelope(s,
|
|
711
|
+
checkLoopListEnvelope(s, registeredSteps, opts, diags);
|
|
719
712
|
if (s.options?.completionCondition)
|
|
720
713
|
addExpr(s.options.completionCondition.js, s.name);
|
|
721
714
|
// The same refusal serialize throws, from the same helper: an option or
|
|
@@ -1377,16 +1370,18 @@ function checkConnectorContract(step, spec, opts, diags) {
|
|
|
1377
1370
|
coreCheckConnectorContract(where, spec, opts, report, step);
|
|
1378
1371
|
checkBindingPresence(`connector "${step}"`, spec, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
|
|
1379
1372
|
}
|
|
1380
|
-
/**
|
|
1381
|
-
|
|
1373
|
+
/**
|
|
1374
|
+
* Reject a wrapper hop when the runtime shape of a list has been measured: the
|
|
1375
|
+
* step the loop reads says what its list output looks like (`listShape`, a
|
|
1376
|
+
* connector operation's measured envelope).
|
|
1377
|
+
*/
|
|
1378
|
+
function checkLoopListEnvelope(loop, registered, opts, diags) {
|
|
1382
1379
|
const read = /^\$vars\.([^.]+)\.output\.([A-Za-z_$][\w$]*)$/.exec(loop.collection.js);
|
|
1383
|
-
if (!read
|
|
1380
|
+
if (!read)
|
|
1384
1381
|
return;
|
|
1385
1382
|
const [, step, field] = read;
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
return;
|
|
1389
|
-
const measured = opts.listEnvelope(connector.key, connector.action, connector.version, connector.object);
|
|
1383
|
+
const source = registered.get(step);
|
|
1384
|
+
const measured = source?.impl.listShape?.(source.spec, opts);
|
|
1390
1385
|
if (!measured || measured.envelope === field)
|
|
1391
1386
|
return;
|
|
1392
1387
|
const shape = measured.envelope === null
|
|
@@ -2723,6 +2718,12 @@ function checkRegisteredStep(entry, s, exprs, errorSource, opts, diags, typeOf,
|
|
|
2723
2718
|
},
|
|
2724
2719
|
options: opts,
|
|
2725
2720
|
bindingLabels: (where, labels, use, target) => checkBindingLabels(where, labels, opts, (code, message) => diags.push({ level: 'error', code, step: s.name, message }), target, use),
|
|
2721
|
+
connector: (c) => {
|
|
2722
|
+
const spec = c;
|
|
2723
|
+
checkConnectorLookups(s.name, spec, opts, diags);
|
|
2724
|
+
checkConnectorSchema(s.name, spec, opts, diags);
|
|
2725
|
+
checkConnectorContract(s.name, spec, opts, diags);
|
|
2726
|
+
},
|
|
2726
2727
|
resources: {
|
|
2727
2728
|
derivedNodeId: (id, what) => { if (!ledger.taken.has(id))
|
|
2728
2729
|
ledger.taken.set(id, what); },
|
|
@@ -2861,39 +2862,7 @@ function collectActionExprs(s, out, errorSource) {
|
|
|
2861
2862
|
push(val);
|
|
2862
2863
|
return;
|
|
2863
2864
|
}
|
|
2864
|
-
if (s.spec.kind === 'connector') {
|
|
2865
|
-
// Same gap on the connector surface: `messageToSend: tmpl`…${v('typo')}``
|
|
2866
|
-
// reached the runtime unchecked.
|
|
2867
|
-
pushDeep(s.spec.inputs);
|
|
2868
|
-
}
|
|
2869
2865
|
}
|
|
2870
|
-
/**
|
|
2871
|
-
* The families with no definition this SDK can ask, and an error port anyway:
|
|
2872
|
-
* an Integration Service connector activity's definition comes from the
|
|
2873
|
-
* connector library, and every connector activity declares the `error` handle.
|
|
2874
|
-
*
|
|
2875
|
-
* Every other family is decided by a definition, never by a list:
|
|
2876
|
-
*
|
|
2877
|
-
* - a bundled family by its bundled definition (`bundledActionNodeType` +
|
|
2878
|
-
* `bundledDefinition`, #1011: `documentClassify` was missing from the old
|
|
2879
|
-
* list although `uipath.document.classify@1.0` sets the flag);
|
|
2880
|
-
* - a per-tenant family by the definition serialize synthesizes for it: a
|
|
2881
|
-
* registered one (the process family, `agent` among it) through its node
|
|
2882
|
-
* type's `errorPort`, which asks that definition, and `ixpExtract` through
|
|
2883
|
-
* `perCallDefinition` (#1040: `publishedFunction` was refused here although
|
|
2884
|
-
* `publishedFunctionDef` sets the flag).
|
|
2885
|
-
*
|
|
2886
|
-
* `publishedFunction`'s flag is measured, not assumed: on 2026-10-08 four
|
|
2887
|
-
* `flow debug` jobs on alpha/codereval called `acme-echo` with a non-string
|
|
2888
|
-
* `message` (null, object, number, array). In each, the function job failed
|
|
2889
|
-
* (`Python.FUNCTION_EXECUTION_ERROR`, category User, status 502), the node's
|
|
2890
|
-
* error boundary fired, and the handler's `err('message')` read
|
|
2891
|
-
* "Failure in the Orchestrator Job".
|
|
2892
|
-
*
|
|
2893
|
-
* A kind in none of these (`waitEvent`) has no error port: an event wait
|
|
2894
|
-
* resumes or does not.
|
|
2895
|
-
*/
|
|
2896
|
-
const NO_DEFINITION_ERROR_PORT_KINDS = new Set(['connector']);
|
|
2897
2866
|
/**
|
|
2898
2867
|
* The ONERROR_NO_ERROR_PORT sentence for a typed step — a built-in family or a
|
|
2899
2868
|
* registered node type — whose node type has no error port. One spelling for
|
|
@@ -2913,10 +2882,33 @@ const ERROR_ENVELOPE_FIELDS = ['code', 'message', 'detail', 'category', 'status'
|
|
|
2913
2882
|
* `.onError(...)`-specific checks. The surface already refuses the structural
|
|
2914
2883
|
* mistakes at build time (no previous step, two handlers on one node, an empty
|
|
2915
2884
|
* handler); these are the ones only a checker can see.
|
|
2885
|
+
*
|
|
2886
|
+
* Whether the step has an error port at all: every family is decided by a
|
|
2887
|
+
* definition, never by a list:
|
|
2888
|
+
*
|
|
2889
|
+
* - a bundled family by its bundled definition (`bundledActionNodeType` +
|
|
2890
|
+
* `bundledDefinition`, #1011: `documentClassify` was missing from the old
|
|
2891
|
+
* list although `uipath.document.classify@1.0` sets the flag);
|
|
2892
|
+
* - a per-tenant family — every one is registered — through its node type's
|
|
2893
|
+
* `errorPort`, which asks the definition serialize synthesizes (#1040:
|
|
2894
|
+
* `publishedFunction` was refused here although `publishedFunctionDef` sets
|
|
2895
|
+
* the flag), or for a connector the library's definition, which check reads
|
|
2896
|
+
* through `connectorDefinition` and, without a library, assumes grants the
|
|
2897
|
+
* port, as every operation the library serves does.
|
|
2898
|
+
*
|
|
2899
|
+
* `publishedFunction`'s flag is measured, not assumed: on 2026-10-08 four
|
|
2900
|
+
* `flow debug` jobs on alpha/codereval called `acme-echo` with a non-string
|
|
2901
|
+
* `message` (null, object, number, array). In each, the function job failed
|
|
2902
|
+
* (`Python.FUNCTION_EXECUTION_ERROR`, category User, status 502), the node's
|
|
2903
|
+
* error boundary fired, and the handler's `err('message')` read
|
|
2904
|
+
* "Failure in the Orchestrator Job".
|
|
2905
|
+
*
|
|
2906
|
+
* A kind in none of these (`waitEvent`) has no error port: an event wait
|
|
2907
|
+
* resumes or does not.
|
|
2916
2908
|
*/
|
|
2917
2909
|
function checkOnError(_handler, step, diags,
|
|
2918
2910
|
/** The step's registry record, when its kind is registered and its version resolved. */
|
|
2919
|
-
placed, carried) {
|
|
2911
|
+
placed, opts, carried) {
|
|
2920
2912
|
// The builder refuses a handler with no eligible step before it, so a missing
|
|
2921
2913
|
// source here means a hand-built `BuiltFlow`; there is nothing to say about it.
|
|
2922
2914
|
if (!step)
|
|
@@ -2986,11 +2978,17 @@ placed, carried) {
|
|
|
2986
2978
|
}
|
|
2987
2979
|
}
|
|
2988
2980
|
else if (impl.definition === 'per-tenant') {
|
|
2989
|
-
|
|
2981
|
+
// The definition serialize will be supplied, when check's options can name
|
|
2982
|
+
// it (a connector's, from the library); the type answers without it otherwise.
|
|
2983
|
+
const def = impl.checkDefinition?.(placed.spec, opts);
|
|
2984
|
+
const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec, def) : impl.errorPort === true;
|
|
2990
2985
|
if (!port) {
|
|
2986
|
+
const named = def !== undefined && typeof def.nodeType === 'string' && typeof def.version === 'string';
|
|
2991
2987
|
diags.push({
|
|
2992
2988
|
level: 'error', code: 'ONERROR_NO_ERROR_PORT', step: step.name,
|
|
2993
|
-
message:
|
|
2989
|
+
message: named
|
|
2990
|
+
? declaredErrorPortRefusal(step.name, def.nodeType, def.version)
|
|
2991
|
+
: declaredErrorPortRefusal(step.name, impl.typeName, impl.version),
|
|
2994
2992
|
});
|
|
2995
2993
|
return;
|
|
2996
2994
|
}
|
|
@@ -3025,7 +3023,7 @@ placed, carried) {
|
|
|
3025
3023
|
// a carried definition); what is left is bundled, or has no definition here.
|
|
3026
3024
|
const noPort = bundledDef !== undefined
|
|
3027
3025
|
? !definitionHasErrorPort(bundledDef)
|
|
3028
|
-
: !bundledType
|
|
3026
|
+
: !bundledType;
|
|
3029
3027
|
if (noPort) {
|
|
3030
3028
|
diags.push({
|
|
3031
3029
|
level: 'error',
|
package/dist/cli-library.js
CHANGED
|
@@ -25,5 +25,13 @@ export function flowCheckOpts(library, bindings) {
|
|
|
25
25
|
return undefined; // an unresolvable operation is connectorResolveError's to report
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
+
connectorDefinition: (connector, operation, version, object) => {
|
|
29
|
+
try {
|
|
30
|
+
return library.resolve(connector, operation, version, object).v1def;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined; // as above
|
|
34
|
+
}
|
|
35
|
+
},
|
|
28
36
|
};
|
|
29
37
|
}
|
|
@@ -27,6 +27,8 @@ export interface BindingsView {
|
|
|
27
27
|
resolve(name?: string): string | undefined;
|
|
28
28
|
declared?(): string[];
|
|
29
29
|
entry?(name?: string): unknown;
|
|
30
|
+
/** True when only a recompile fallback row (the plan's own bindings) answers `name`. */
|
|
31
|
+
fromFallback?(name?: string): boolean;
|
|
30
32
|
}
|
|
31
33
|
export declare function bindingSelfNameMessage(where: string, name: string, attribute: 'connection' | 'folder', target?: PrepareTarget): string;
|
|
32
34
|
/**
|
|
@@ -81,6 +81,11 @@ export function undeclaredBinding(bindings, name) {
|
|
|
81
81
|
export function bindingLabelRefusal(bindings, name, attribute, where, target) {
|
|
82
82
|
if (!bindings)
|
|
83
83
|
return undefined;
|
|
84
|
+
// A label that is exactly a row id of the plan being recompiled (what
|
|
85
|
+
// decompile emits) reuses that row. Those rows are not an author's
|
|
86
|
+
// bindings.json, so neither rule applies to them.
|
|
87
|
+
if (bindings.fromFallback?.(name))
|
|
88
|
+
return undefined;
|
|
84
89
|
const resolved = bindings.resolve(name);
|
|
85
90
|
if (resolved === name)
|
|
86
91
|
return new BindingRefusal('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
|
package/dist/core/bindings.d.ts
CHANGED
|
@@ -1,11 +1,50 @@
|
|
|
1
1
|
export declare class Bindings {
|
|
2
2
|
private list;
|
|
3
|
+
/**
|
|
4
|
+
* Rows from {@link withFallback}: the root `bindings[]` of the plan being
|
|
5
|
+
* recompiled. Kept apart from `list` on purpose. They are not an author's
|
|
6
|
+
* bindings.json, so they never count as declared names and never feed the
|
|
7
|
+
* self-name / undeclared label rules. An offline plan's own rows are
|
|
8
|
+
* `{ id: 'slack', resourceKey: 'slack' }`; counted as declared they made the
|
|
9
|
+
* second offline compile refuse the connector's own label (BINDING_SELF_NAME).
|
|
10
|
+
*/
|
|
11
|
+
private fallback;
|
|
3
12
|
constructor(file?: string);
|
|
4
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Add the root bindings of another document (the plan being recompiled) as a
|
|
15
|
+
* fallback. A fallback row answers only a label that is exactly its `id`
|
|
16
|
+
* (what decompile emits), whose key is not that same id (a placeholder; see
|
|
17
|
+
* `fallbackRow`), and that the primary registry does not answer. It
|
|
18
|
+
* is never part of {@link declared}, and {@link fromFallback} lets the label
|
|
19
|
+
* rules skip it.
|
|
20
|
+
*
|
|
21
|
+
* @param file - A bindings.json, or any document with a root `bindings[]`
|
|
22
|
+
* (a caseplan); a missing or unreadable file adds nothing.
|
|
23
|
+
* @param isPlaceholder - Recognises a row the document's own compiler minted
|
|
24
|
+
* offline (a label with no real key behind it, under a minted id). Such a
|
|
25
|
+
* row answers nothing, like a row whose key is its own id.
|
|
26
|
+
* @returns This registry, for chaining.
|
|
27
|
+
*/
|
|
28
|
+
withFallback(file?: string, isPlaceholder?: (row: Record<string, unknown>) => boolean): this;
|
|
29
|
+
private primary;
|
|
30
|
+
/**
|
|
31
|
+
* The fallback row whose `id` is `name`. A row whose key is its own id
|
|
32
|
+
* (`{ id: 'drive', resourceKey: 'drive' }`, what an offline compile writes) is
|
|
33
|
+
* a placeholder, not an answer, so it is skipped: the label rules then judge
|
|
34
|
+
* `drive` against the author's bindings.json as they would without a fallback.
|
|
35
|
+
*/
|
|
36
|
+
private fallbackRow;
|
|
37
|
+
/**
|
|
38
|
+
* True when only a fallback row answers `name`: the primary registry does not,
|
|
39
|
+
* and a fallback row's `id` is exactly `name`. The label rules
|
|
40
|
+
* (`bindingLabelRefusal`) accept such a label as is.
|
|
41
|
+
*/
|
|
42
|
+
fromFallback(name?: string): boolean;
|
|
43
|
+
/** Resolve by binding `name` or `id` (fallback rows: `id` only); returns its resourceKey (or default). */
|
|
5
44
|
resolve(name?: string): string | undefined;
|
|
6
|
-
/** Every symbolic name the
|
|
45
|
+
/** Every symbolic name the bindings.json declares — what an author CAN write in `connection:` / `folder:`. Fallback rows are not included. */
|
|
7
46
|
declared(): string[];
|
|
8
|
-
/** The full binding entry by `name` or `id` — for consumers that emit a binding
|
|
47
|
+
/** The full binding entry by `name` or `id` (fallback rows: `id` only) — for consumers that emit a binding
|
|
9
48
|
* registry + `=bindings.<id>` refs (the Case caseplan) rather than inlining the
|
|
10
49
|
* resourceKey (the Flow node). */
|
|
11
50
|
entry(name?: string): Record<string, unknown> | undefined;
|
package/dist/core/bindings.js
CHANGED
|
@@ -5,21 +5,83 @@
|
|
|
5
5
|
import { readFileSync, existsSync } from 'node:fs';
|
|
6
6
|
export class Bindings {
|
|
7
7
|
list = [];
|
|
8
|
+
/**
|
|
9
|
+
* Rows from {@link withFallback}: the root `bindings[]` of the plan being
|
|
10
|
+
* recompiled. Kept apart from `list` on purpose. They are not an author's
|
|
11
|
+
* bindings.json, so they never count as declared names and never feed the
|
|
12
|
+
* self-name / undeclared label rules. An offline plan's own rows are
|
|
13
|
+
* `{ id: 'slack', resourceKey: 'slack' }`; counted as declared they made the
|
|
14
|
+
* second offline compile refuse the connector's own label (BINDING_SELF_NAME).
|
|
15
|
+
*/
|
|
16
|
+
fallback = [];
|
|
8
17
|
constructor(file) {
|
|
9
18
|
if (file && existsSync(file)) {
|
|
10
19
|
this.list = normalizeBindings(JSON.parse(readFileSync(file, 'utf8')));
|
|
11
20
|
}
|
|
12
21
|
}
|
|
13
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Add the root bindings of another document (the plan being recompiled) as a
|
|
24
|
+
* fallback. A fallback row answers only a label that is exactly its `id`
|
|
25
|
+
* (what decompile emits), whose key is not that same id (a placeholder; see
|
|
26
|
+
* `fallbackRow`), and that the primary registry does not answer. It
|
|
27
|
+
* is never part of {@link declared}, and {@link fromFallback} lets the label
|
|
28
|
+
* rules skip it.
|
|
29
|
+
*
|
|
30
|
+
* @param file - A bindings.json, or any document with a root `bindings[]`
|
|
31
|
+
* (a caseplan); a missing or unreadable file adds nothing.
|
|
32
|
+
* @param isPlaceholder - Recognises a row the document's own compiler minted
|
|
33
|
+
* offline (a label with no real key behind it, under a minted id). Such a
|
|
34
|
+
* row answers nothing, like a row whose key is its own id.
|
|
35
|
+
* @returns This registry, for chaining.
|
|
36
|
+
*/
|
|
37
|
+
withFallback(file, isPlaceholder) {
|
|
38
|
+
if (!file || !existsSync(file))
|
|
39
|
+
return this;
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return this; // a fallback is optional; an unreadable one adds nothing
|
|
46
|
+
}
|
|
47
|
+
const ids = new Set(this.fallback.map((x) => x?.id));
|
|
48
|
+
for (const entry of normalizeBindings(parsed))
|
|
49
|
+
if (typeof entry?.id === 'string' && !ids.has(entry.id) && !isPlaceholder?.(entry))
|
|
50
|
+
this.fallback.push(entry);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
primary(name) {
|
|
54
|
+
return this.list.find((x) => x.name === name || x.id === name);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fallback row whose `id` is `name`. A row whose key is its own id
|
|
58
|
+
* (`{ id: 'drive', resourceKey: 'drive' }`, what an offline compile writes) is
|
|
59
|
+
* a placeholder, not an answer, so it is skipped: the label rules then judge
|
|
60
|
+
* `drive` against the author's bindings.json as they would without a fallback.
|
|
61
|
+
*/
|
|
62
|
+
fallbackRow(name) {
|
|
63
|
+
return this.fallback.find((x) => x.id === name && (x.resourceKey ?? x.default) !== name);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* True when only a fallback row answers `name`: the primary registry does not,
|
|
67
|
+
* and a fallback row's `id` is exactly `name`. The label rules
|
|
68
|
+
* (`bindingLabelRefusal`) accept such a label as is.
|
|
69
|
+
*/
|
|
70
|
+
fromFallback(name) {
|
|
71
|
+
if (!name)
|
|
72
|
+
return false;
|
|
73
|
+
return this.primary(name) === undefined && this.fallbackRow(name) !== undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Resolve by binding `name` or `id` (fallback rows: `id` only); returns its resourceKey (or default). */
|
|
14
76
|
resolve(name) {
|
|
15
77
|
if (!name)
|
|
16
78
|
return undefined;
|
|
17
|
-
const b = this.
|
|
79
|
+
const b = this.primary(name) ?? this.fallbackRow(name);
|
|
18
80
|
if (!b)
|
|
19
81
|
return undefined;
|
|
20
82
|
return (b.resourceKey ?? b.default);
|
|
21
83
|
}
|
|
22
|
-
/** Every symbolic name the
|
|
84
|
+
/** Every symbolic name the bindings.json declares — what an author CAN write in `connection:` / `folder:`. Fallback rows are not included. */
|
|
23
85
|
declared() {
|
|
24
86
|
const names = [];
|
|
25
87
|
for (const b of this.list) {
|
|
@@ -29,13 +91,13 @@ export class Bindings {
|
|
|
29
91
|
}
|
|
30
92
|
return names;
|
|
31
93
|
}
|
|
32
|
-
/** The full binding entry by `name` or `id` — for consumers that emit a binding
|
|
94
|
+
/** The full binding entry by `name` or `id` (fallback rows: `id` only) — for consumers that emit a binding
|
|
33
95
|
* registry + `=bindings.<id>` refs (the Case caseplan) rather than inlining the
|
|
34
96
|
* resourceKey (the Flow node). */
|
|
35
97
|
entry(name) {
|
|
36
98
|
if (!name)
|
|
37
99
|
return undefined;
|
|
38
|
-
return this.
|
|
100
|
+
return (this.primary(name) ?? this.fallbackRow(name));
|
|
39
101
|
}
|
|
40
102
|
}
|
|
41
103
|
/**
|
|
@@ -46,6 +46,8 @@ export interface ConnectorInputOpts {
|
|
|
46
46
|
}
|
|
47
47
|
/** Flow's dialect, re-exported so `serialize.ts` need not reach past this module. */
|
|
48
48
|
export { FLOW_EXPR_DIALECT } from './connector-checks.js';
|
|
49
|
+
/** A required connector input left empty — the one draft code with no error class of its own. */
|
|
50
|
+
export declare const CONNECTOR_INPUT_MISSING = "CONNECTOR_INPUT_MISSING";
|
|
49
51
|
/**
|
|
50
52
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
51
53
|
* would accept them — the SAME code path `buildConnectorInputs` runs at emit time,
|
|
@@ -30,6 +30,8 @@ export { FLOW_EXPR_DIALECT } from './connector-checks.js';
|
|
|
30
30
|
import { FLOW_EXPR_DIALECT as FLOW_DIALECT } from './connector-checks.js';
|
|
31
31
|
import { ownEntry } from './own-entry.js';
|
|
32
32
|
import { isRecord } from './node-envelope.js';
|
|
33
|
+
/** A required connector input left empty — the one draft code with no error class of its own. */
|
|
34
|
+
export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
|
|
33
35
|
/**
|
|
34
36
|
* Validate a connector's activity inputs against its declared field list and
|
|
35
37
|
* return them ROUTED to the wire slots the platform reads. Catches the three
|
|
@@ -106,6 +106,8 @@ export interface FactoryCall {
|
|
|
106
106
|
readonly factory: string;
|
|
107
107
|
readonly positional?: readonly string[];
|
|
108
108
|
readonly options?: ReadonlyArray<readonly [name: string, source: string | undefined]>;
|
|
109
|
+
/** A note for the reader, written as a block comment after the call (the artifact escapes it). */
|
|
110
|
+
readonly comment?: string;
|
|
109
111
|
}
|
|
110
112
|
/** The contexts an artifact lends its node types, one per job. */
|
|
111
113
|
export interface ArtifactContexts {
|
|
@@ -131,8 +133,11 @@ export interface NodeType<Spec, Serialized, Ctx extends ArtifactContexts> {
|
|
|
131
133
|
* Whether the node has an error port. A `bundled` type must omit it — the answer
|
|
132
134
|
* is the definition's, at this version. A `per-tenant` type must declare it,
|
|
133
135
|
* because there is no definition to ask. The registry refuses either mistake.
|
|
136
|
+
* Serialize passes the definition the type supplied as `definition`, for a type
|
|
137
|
+
* whose definition comes from outside the SDK (a connector's, from its
|
|
138
|
+
* library); check, which has no definition, passes none.
|
|
134
139
|
*/
|
|
135
|
-
readonly errorPort?: boolean | ((spec: Spec) => boolean);
|
|
140
|
+
readonly errorPort?: boolean | ((spec: Spec, definition?: unknown) => boolean);
|
|
136
141
|
/** Spec → the node's type-owned fields. The artifact still owns id, label, layout, edges and dedupe. */
|
|
137
142
|
serialize(spec: Spec, ctx: Ctx['serialize']): Serialized;
|
|
138
143
|
/** The lift: one serialized node → the spec it came from, or `undefined` when it is not this type's. */
|
|
@@ -33,6 +33,13 @@ Impl extends NodeType<Spec, Serialized, Ctx> = NodeType<Spec, Serialized, Ctx>>
|
|
|
33
33
|
* exact name sit under the prefix as long as this rejects it.
|
|
34
34
|
*/
|
|
35
35
|
typeNameMatches?(typeName: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* One implementation answers for every version: the definition the type
|
|
38
|
+
* supplies names the node's version, as a connector operation's library entry
|
|
39
|
+
* does (1.0.0 for most, 4.0.0 for some). The registry then neither defaults nor
|
|
40
|
+
* refuses a `{ version }`; serialize holds the pin to that definition.
|
|
41
|
+
*/
|
|
42
|
+
readonly versionedByDefinition?: boolean;
|
|
36
43
|
readonly factory: string;
|
|
37
44
|
/** The version used when the author names none. Changing it is a breaking change. */
|
|
38
45
|
readonly defaultVersion: string;
|
|
@@ -25,6 +25,11 @@ export function asVersioned(t) {
|
|
|
25
25
|
* node type's bug, not the author's, and throws as an internal SDK error.
|
|
26
26
|
*/
|
|
27
27
|
export function resolveVersion(t, spec, requested, where) {
|
|
28
|
+
if (t.versionedByDefinition) {
|
|
29
|
+
const only = ownEntry(t.versions, t.defaultVersion);
|
|
30
|
+
if (only)
|
|
31
|
+
return only;
|
|
32
|
+
}
|
|
28
33
|
const selected = requested === undefined ? t.select?.(spec) : undefined;
|
|
29
34
|
const version = requested ?? selected ?? t.defaultVersion;
|
|
30
35
|
const impl = ownEntry(t.versions, version);
|
|
@@ -63,6 +68,12 @@ export function resolveVersion(t, spec, requested, where) {
|
|
|
63
68
|
* implementation.
|
|
64
69
|
*/
|
|
65
70
|
export function resolveVersionCarried(t, spec, requested, where, carried) {
|
|
71
|
+
// The definition names the version (see versionedByDefinition): a pin is the
|
|
72
|
+
// definition's to accept or refuse, not the registry's.
|
|
73
|
+
if (t.versionedByDefinition) {
|
|
74
|
+
const impl = resolveVersion(t, spec, undefined, where);
|
|
75
|
+
return { impl, version: requested ?? impl.version };
|
|
76
|
+
}
|
|
66
77
|
if (requested !== undefined && !ownEntry(t.versions, requested)) {
|
|
67
78
|
const def = carried?.(t.typeName, requested);
|
|
68
79
|
if (def !== undefined) {
|