@uipath/maestro-builder-sdk 6.51.2 → 6.52.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.
@@ -542,8 +542,18 @@ function connectorInputs(slots, resolved, owner) {
542
542
  for (const parameter of resolved.entry.operation?.parameters ?? [])
543
543
  parameterByWireName.set(toCamel(String(parameter.name)), String(parameter.name));
544
544
  const inputs = {};
545
+ const fileParts = new Set(resolved.multipartParts
546
+ ?.filter((part) => part.dataType.toLowerCase() === 'file').map((part) => part.name));
545
547
  for (const slot of slots) {
546
548
  const name = String(slot?.name ?? '');
549
+ // A multipart file part's input (`target: "file"`), named after the part.
550
+ // Its value is the authored input; a valueless one is only the declaration
551
+ // that serialize writes for every file part.
552
+ if (slot?.type === 'file' && slot.target === 'file' && fileParts.has(name)) {
553
+ if (slot.value !== undefined)
554
+ inputs[name] = slot.value;
555
+ continue;
556
+ }
547
557
  if (owner && !CONNECTOR_INPUT_CONTAINERS.has(name))
548
558
  throw new Error(`${owner} has unsupported input row "${name}".`);
549
559
  const body = slot?.body;
@@ -1414,7 +1414,7 @@ function connectorTaskData(spec, resolver, elementId, where, executionType) {
1414
1414
  const folderRef = folderBindingRef(resolver, spec.folder, connRef, { attribute: 'folder', where, target });
1415
1415
  if (folderRef)
1416
1416
  context.push(io('folderKey', 'string', { value: `=bindings.${folderRef.id}` }));
1417
- context.push(io('objectName', 'string', { value: op.objectName ?? spec.action }), io('method', 'string', { value: transportHttpMethod(rc.httpMethod) }), io('path', 'string', { value: rc.endpoint }), io('metadata', 'json', { body: connectorMetadata(rc, op, executionType) }));
1417
+ context.push(io('objectName', 'string', { value: op.objectName ?? spec.action }), io('method', 'string', { value: transportHttpMethod(rc.httpMethod) }), io('path', 'string', { value: rc.endpoint }));
1418
1418
  // inputs: the shared validator (`core/connector-inputs.ts`), the one Flow's and
1419
1419
  // BPMN's serializers and every family's `check` run. It refuses a missing
1420
1420
  // required field, an unknown or mistyped input, and swaps a `lookup()` token for
@@ -1435,15 +1435,21 @@ function connectorTaskData(spec, resolver, elementId, where, executionType) {
1435
1435
  const camelKeys = (slot) => Object.fromEntries(Object.entries(slot ?? {}).map(([name, value]) => [toCamel(name), value]));
1436
1436
  const pathParameters = camelKeys(slots.pathParameters);
1437
1437
  const queryParameters = camelKeys(slots.queryParameters);
1438
- // Case has no multipart wire: a filled file part stays in the body, where it
1439
- // has always gone.
1440
- const parts = Object.fromEntries((slots.multipartParameters ?? [])
1441
- .filter((part) => part.value !== undefined).map((part) => [part.name, part.value]));
1442
- const body = nestConnectorBody({ ...(slots.bodyParameters ?? {}), ...parts });
1438
+ const body = nestConnectorBody(slots.bodyParameters ?? {});
1439
+ // A multipart operation travels on the multipart wire, as the Case Designer
1440
+ // and `uip maestro case` save it: the parts declared in the metadata's
1441
+ // `multipartParameters`, `inputMetadata` naming the container part, and one
1442
+ // `file` input per FILE part, carrying the author's value when there is one.
1443
+ // The container part (`body`) gets no input of its own: the runtime builds it
1444
+ // from the `body` slot. This used to drop the whole wire and push a filled
1445
+ // file part into the body, where the runtime does not look for it (#1420).
1446
+ const fileParts = (slots.multipartParameters ?? []).filter((part) => part.dataType.toLowerCase() === 'file');
1447
+ context.push(io('metadata', 'json', { body: connectorMetadata(rc, op, slots, executionType) }));
1443
1448
  const inputs = [
1444
1449
  ioSlot('pathParameters', pathParameters, `${elementId}`),
1445
1450
  ioSlot('queryParameters', queryParameters, `${elementId}`),
1446
1451
  ioSlot('body', body, `${elementId}`),
1452
+ ...fileParts.map((part) => fileSlot(part, elementId)),
1447
1453
  ];
1448
1454
  const outputs = connectorOutputs(rc, elementId, resolver);
1449
1455
  return { serviceType: CASE_FORMAT_PROFILE.connectorActivityServiceType, context, inputs, outputs, bindings: [] };
@@ -1487,6 +1493,20 @@ function ioSlot(name, bodyObj, elementId) {
1487
1493
  const v = `v${name}`;
1488
1494
  return { name, type: 'json', target: name, body: bodyObj, var: v, id: v, elementId };
1489
1495
  }
1496
+ /**
1497
+ * An `inputs[]` row for a multipart FILE part. It is named after the part and
1498
+ * targets `file`, the one input whose `name` and `target` differ (the CLI's
1499
+ * `case validate` reads the file channel by that `target`). A part the author
1500
+ * left empty is still declared, valueless, as the Designer saves it.
1501
+ */
1502
+ function fileSlot(part, elementId) {
1503
+ const v = `v${part.name}`;
1504
+ return {
1505
+ name: part.name, type: 'file', target: 'file',
1506
+ ...(part.value !== undefined ? { value: part.value } : {}),
1507
+ var: v, id: v, elementId,
1508
+ };
1509
+ }
1490
1510
  /**
1491
1511
  * The binding id for a label no bindings.json declares. The row's
1492
1512
  * `resourceKey`/`default` stays the label, and the id is minted from it (`b` +
@@ -1582,9 +1602,12 @@ function folderBindingRef(resolver, name, connection, site) {
1582
1602
  return { id };
1583
1603
  }
1584
1604
  /** The connector `metadata` json blob: the FUNCTIONAL essentialConfiguration +
1585
- * activityTypeId. Designer cosmetics (packageId/activityColor/…) are omitted —
1586
- * the caseplan schema-validates without them (see design §C). */
1587
- function connectorMetadata(rc, op, executionType) {
1605
+ * activityTypeId, and the multipart wire of a multipart operation. Designer
1606
+ * cosmetics (packageId/activityColor/…) are omitted — the caseplan
1607
+ * schema-validates without them (see design §C). */
1608
+ function connectorMetadata(rc, op, slots, executionType) {
1609
+ // The part DECLARATIONS only: a file part's value rides on its `file` input.
1610
+ const multipartParameters = (slots.multipartParameters ?? []).map(({ name, dataType }) => ({ name, dataType }));
1588
1611
  const instanceParameters = {
1589
1612
  connectorKey: rc.connectorKey,
1590
1613
  objectName: op.objectName ?? rc.nodeType.split('.').pop(),
@@ -1608,6 +1631,7 @@ function connectorMetadata(rc, op, executionType) {
1608
1631
  };
1609
1632
  return {
1610
1633
  activityPropertyConfiguration: {
1634
+ ...(multipartParameters.length ? { multipartParameters } : {}),
1611
1635
  configuration: `=jsonString:${JSON.stringify(configuration)}`,
1612
1636
  uiPathActivityTypeId: rc.activityTypeId ?? '',
1613
1637
  errorState: { issues: [] },
@@ -1623,6 +1647,9 @@ function connectorMetadata(rc, op, executionType) {
1623
1647
  isEnabled: true,
1624
1648
  },
1625
1649
  },
1650
+ // `{}` unless the operation is multipart, as the Designer saves every
1651
+ // connector activity, external tasks included.
1652
+ inputMetadata: slots.inputMetadata ?? {},
1626
1653
  telemetryData: {
1627
1654
  connectorKey: rc.connectorKey,
1628
1655
  connectorName: rc.connectorName,
package/dist/check.d.ts CHANGED
@@ -52,3 +52,11 @@ export declare const SCHEDULE_PATTERN: string;
52
52
  * and day names, and the Quartz specials `L W LW ? * #`.
53
53
  */
54
54
  export declare const SCHEDULE_CRON_PATTERN: string;
55
+ /**
56
+ * The one thing `check` says about runtimes: a flow with a node from a node-type
57
+ * package does not run on the UiPath platform, which runs exactly its registry.
58
+ * Reported once per flow, naming the steps and their packages, as a WARNING: the
59
+ * flow is still a correct workflow for a runtime that loads the package (the
60
+ * local engine), it just cannot be validated or published through `uip`.
61
+ */
62
+ export declare const PACKAGE_NODES_NOT_PLATFORM = "PACKAGE_NODES_NOT_PLATFORM";
package/dist/check.js CHANGED
@@ -892,6 +892,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
892
892
  }
893
893
  reportUnreachableHitlOutcomes(hitlChoiceNoPorts, exprs, diags);
894
894
  checkRegisteredFamilies(registeredSteps, unresolvedSteps, stepNames, diags);
895
+ checkPackageNodes(registeredSteps, diags);
895
896
  // The ids this scope's other nodes get, for the file-wide nodeId check in `check`.
896
897
  for (const n of stepNames)
897
898
  if (!ledger.taken.has(n))
@@ -2819,6 +2820,36 @@ function catalogued(entry, step, code, message, suggestion) {
2819
2820
  // Key order as the per-family rules always wrote it — `check --json` prints it.
2820
2821
  return { level: known.severity, code, step, message, ...(suggestion !== undefined ? { suggestion } : {}) };
2821
2822
  }
2823
+ /**
2824
+ * The one thing `check` says about runtimes: a flow with a node from a node-type
2825
+ * package does not run on the UiPath platform, which runs exactly its registry.
2826
+ * Reported once per flow, naming the steps and their packages, as a WARNING: the
2827
+ * flow is still a correct workflow for a runtime that loads the package (the
2828
+ * local engine), it just cannot be validated or published through `uip`.
2829
+ */
2830
+ export const PACKAGE_NODES_NOT_PLATFORM = 'PACKAGE_NODES_NOT_PLATFORM';
2831
+ function checkPackageNodes(steps, diags) {
2832
+ const byPackage = new Map();
2833
+ let first;
2834
+ for (const [name, placed] of steps) {
2835
+ const origin = placed.entry.family.package;
2836
+ if (origin === undefined)
2837
+ continue;
2838
+ first ??= name;
2839
+ const list = byPackage.get(origin.name) ?? [];
2840
+ list.push(`"${name}" (${placed.impl.typeName})`);
2841
+ byPackage.set(origin.name, list);
2842
+ }
2843
+ if (first === undefined)
2844
+ return;
2845
+ const packages = [...byPackage].map(([pkg, names]) => `${pkg}: ${names.join(', ')}`).join('; ');
2846
+ diags.push({
2847
+ level: 'warning', code: PACKAGE_NODES_NOT_PLATFORM, step: first,
2848
+ message: `This flow uses node types from a node-type package (${packages}). The UiPath platform runs only its own registry, `
2849
+ + 'so `uip maestro flow validate` refuses the file and it cannot be published; it runs on a runtime that loads the package, '
2850
+ + 'such as the local engine (`flow-debug`).',
2851
+ });
2852
+ }
2822
2853
  /** Run each registered family's own rules over all of its placements in this flow. */
2823
2854
  function checkRegisteredFamilies(steps, unresolved, stepNames, diags) {
2824
2855
  const byFamily = new Map();
@@ -2969,7 +3000,7 @@ placed, opts, carried) {
2969
3000
  if (!placed)
2970
3001
  return;
2971
3002
  const { impl } = placed;
2972
- if (impl.definition === 'per-tenant' && placed.carried !== undefined) {
3003
+ if (impl.definition !== 'bundled' && placed.carried !== undefined) {
2973
3004
  // At a carried version the carried definition answers, as serialize asks
2974
3005
  // it: the type's declaration covers only the versions it registers.
2975
3006
  if (!definitionHasErrorPort(placed.carried)) {
@@ -2977,7 +3008,7 @@ placed, opts, carried) {
2977
3008
  return;
2978
3009
  }
2979
3010
  }
2980
- else if (impl.definition === 'per-tenant') {
3011
+ else if (impl.definition !== 'bundled') {
2981
3012
  // The definition serialize will be supplied, when check's options can name
2982
3013
  // it (a connector's, from the library); the type answers without it otherwise.
2983
3014
  const def = impl.checkDefinition?.(placed.spec, opts);
@@ -92,6 +92,7 @@ export function registerFlowCommands(program) {
92
92
  .option('--import <specifier>', 'SDK import specifier in generated source')
93
93
  .option('--strict', 'fail on unsupported constructs')
94
94
  .option('--no-pipeline', 'do not emit a brownfield pipeline helper')
95
+ .option('--package <name...>', 'trust and load this node-type package (repeatable); otherwise package.json "maestroBuilderSdk".nodePackages decides')
95
96
  .action(async (input, options) => {
96
97
  const argv = [input];
97
98
  addValue(argv, '-o', options.output);
@@ -100,6 +101,8 @@ export function registerFlowCommands(program) {
100
101
  argv.push('--strict');
101
102
  if (options.pipeline === false)
102
103
  argv.push('--no-pipeline');
104
+ for (const name of options.package ?? [])
105
+ argv.push('--package', name);
103
106
  await invoke('flow decompile', () => import('../../decompile-cli.js'), argv);
104
107
  });
105
108
  flow.command('merge')
@@ -112,6 +115,7 @@ export function registerFlowCommands(program) {
112
115
  .option('--library <dir>', 'connector library, for compiling with --baseline-from or a .flow.ts edited source')
113
116
  .option('--bindings <file>', 'bindings.json, for the same compiles')
114
117
  .option('--strict', 'refuse when the no-edit round trip (the baseline) differs from the original')
118
+ .option('--package <name...>', 'trust and load this node-type package for --baseline-from (repeatable)')
115
119
  .allowExcessArguments()
116
120
  .action(async (_edited, options, command) => {
117
121
  const pair = namedOrLegacySecond(command, {
@@ -129,6 +133,8 @@ export function registerFlowCommands(program) {
129
133
  addValue(argv, '--bindings', options.bindings);
130
134
  if (options.strict)
131
135
  argv.push('--strict');
136
+ for (const name of options.package ?? [])
137
+ argv.push('--package', name);
132
138
  await invoke('flow merge', () => import('../../merge-cli.js'), argv);
133
139
  });
134
140
  }
package/dist/cli-run.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export type CliExitCode = 0 | 1 | 2;
2
2
  export declare function runWhenInvokedDirectly(moduleUrl: string, label: string, run: (argv: string[]) => Promise<CliExitCode> | CliExitCode): void;
3
3
  /** The value after `name` in `argv`, or `undefined`. */
4
+ /** Every value of a repeatable flag, in order: `--package a --package b` → `['a', 'b']`. */
5
+ export declare function flagValues(argv: readonly string[], flag: string): string[];
4
6
  export declare function flagValue(argv: readonly string[], name: string): string | undefined;
package/dist/cli-run.js CHANGED
@@ -51,6 +51,14 @@ export function runWhenInvokedDirectly(moduleUrl, label, run) {
51
51
  });
52
52
  }
53
53
  /** The value after `name` in `argv`, or `undefined`. */
54
+ /** Every value of a repeatable flag, in order: `--package a --package b` → `['a', 'b']`. */
55
+ export function flagValues(argv, flag) {
56
+ const out = [];
57
+ for (let i = 0; i < argv.length - 1; i++)
58
+ if (argv[i] === flag)
59
+ out.push(argv[i + 1]);
60
+ return out;
61
+ }
54
62
  export function flagValue(argv, name) {
55
63
  const i = argv.indexOf(name);
56
64
  return i >= 0 ? argv[i + 1] : undefined;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The hand-off between a node-type package and the artifact's registry.
3
+ *
4
+ * `@uipath/maestro-builder-sdk/extend` is a builder entry and stays light at
5
+ * import (FB009): it must not load the Flow registry, which pulls the bundled
6
+ * definitions in. So a package's family is CONTRIBUTED here, and the registry
7
+ * ADOPTS whatever was contributed when it loads — at compile time, before any
8
+ * flow is built — and installs itself as the sink for every later contribution,
9
+ * which it then registers at once. A refusal (a kind a built-in family owns, a
10
+ * type that is not `definition: 'package'`) surfaces at adoption, still before
11
+ * the first node is serialized.
12
+ */
13
+ import type { ArtifactContexts } from './node-type.js';
14
+ import type { NodeFamily } from './family.js';
15
+ type Sink = (family: NodeFamily<ArtifactContexts>) => void;
16
+ /** Hand a package's family to the artifact's registry, now or when it loads. */
17
+ export declare function contributeFamily(family: NodeFamily<ArtifactContexts>): void;
18
+ /**
19
+ * The registry calls this once at load: it receives every family contributed so
20
+ * far and every one contributed after. Families are adopted in contribution order.
21
+ */
22
+ export declare function adoptContributedFamilies(adopt: Sink): void;
23
+ export {};
@@ -0,0 +1,18 @@
1
+ const pending = [];
2
+ let sink;
3
+ /** Hand a package's family to the artifact's registry, now or when it loads. */
4
+ export function contributeFamily(family) {
5
+ if (sink !== undefined)
6
+ sink(family);
7
+ else
8
+ pending.push(family);
9
+ }
10
+ /**
11
+ * The registry calls this once at load: it receives every family contributed so
12
+ * far and every one contributed after. Families are adopted in contribution order.
13
+ */
14
+ export function adoptContributedFamilies(adopt) {
15
+ sink = adopt;
16
+ for (const family of pending.splice(0))
17
+ adopt(family);
18
+ }
@@ -25,12 +25,25 @@ export interface Placement<Spec = unknown> {
25
25
  /** The version the node will be emitted at. */
26
26
  readonly version: string;
27
27
  }
28
+ /** The npm package a node family comes from, when it is not built into the SDK. */
29
+ export interface PackageOrigin {
30
+ /** The package name an author imports, e.g. `'@uipath/flow-local-nodes'`. */
31
+ readonly name: string;
32
+ /** The package's version, when it knows it; written into each definition's provenance. */
33
+ readonly version?: string;
34
+ }
28
35
  export interface NodeFamily<Ctx extends ArtifactContexts> {
29
36
  /** Stable id, e.g. `'data-fabric'`. Unique within the artifact's registry. */
30
37
  readonly id: string;
31
38
  readonly title: string;
32
39
  readonly types: readonly AnyNodeType<Ctx>[];
33
40
  readonly diagnostics: DiagnosticCatalog;
41
+ /**
42
+ * Set on a family registered from a node-type package. Every type of such a
43
+ * family declares `definition: 'package'`, and only such a family may; a
44
+ * built-in family never sets this.
45
+ */
46
+ readonly package?: PackageOrigin;
34
47
  /** Rules over every placement of this family's types, after the per-node checks. */
35
48
  check?(placements: readonly Placement[], ctx: Ctx['familyCheck']): void;
36
49
  }
@@ -25,7 +25,19 @@ import type { TypeDesc } from '../expr.js';
25
25
  * its declared inputs) is read from. `per-tenant`: built per tenant or per call —
26
26
  * there is no bundled entry to ask, so the node type declares those answers itself.
27
27
  */
28
- export type DefinitionSource = 'bundled' | 'per-tenant';
28
+ /**
29
+ * Where a node type's definition comes from.
30
+ *
31
+ * - `bundled`: the SDK's bundled registry snapshot has it, so the node carries no
32
+ * definition and the bundle answers `errorPort`.
33
+ * - `per-tenant`: a connector-shaped type the tenant defines; the type supplies its
34
+ * definition at serialize time and declares `errorPort`.
35
+ * - `package`: a type from a node-type package (`@uipath/maestro-builder-sdk/extend`);
36
+ * the package ships the definition, the type writes it into the file with its
37
+ * provenance, and the UiPath platform does not run it. A built-in family may not
38
+ * declare one — the registry refuses it (see `NodeTypeRegistry`).
39
+ */
40
+ export type DefinitionSource = 'bundled' | 'per-tenant' | 'package';
29
41
  /**
30
42
  * One place a spec carries a value the artifact evaluates against its scope.
31
43
  * `value` is walked deeply for expressions; `code` is a script body whose
@@ -104,6 +116,12 @@ export type OutputContract = {
104
116
  */
105
117
  export interface FactoryCall {
106
118
  readonly factory: string;
119
+ /**
120
+ * The module the factory is imported from, when it is not the SDK: a node-type
121
+ * package's own name. Decompile defaults it to the type's family `package.name`,
122
+ * so a `print` implementation sets it only to import from somewhere else.
123
+ */
124
+ readonly module?: string;
107
125
  readonly positional?: readonly string[];
108
126
  readonly options?: ReadonlyArray<readonly [name: string, source: string | undefined]>;
109
127
  /** A note for the reader, written as a block comment after the call (the artifact escapes it). */
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The node definition a node-type package writes into a compiled `.flow`.
3
+ *
4
+ * A package type has no platform definition — neither the SDK's bundled snapshot
5
+ * nor the tenant has one — so the file has to carry everything a reader needs:
6
+ * the designer's display and handles, the input and output schemas, and the
7
+ * `model.expansion` template the `.flow` → BPMN converter expands.
8
+ *
9
+ * ## Why one-node expansion
10
+ *
11
+ * The product's converter (vendored in `local-engine`) resolves a node's
12
+ * definition as `manifestDef ?? fileDef` and reads a node's per-instance input
13
+ * VALUES only through a definition's `model.expansion`: a `context` entry's
14
+ * `source: 'node.data.inputs.<name>'` is read off each placed node while the
15
+ * template is expanded. So every package definition expands to exactly one
16
+ * `bpmn:ServiceTask` that REPLACES the node under its own id, carries
17
+ * `<uipath:type value="<serviceType>">`, its inputs as context, and its
18
+ * `=response` / `=Error` output rows — the element shape a runtime's dispatch
19
+ * lane reads for a connector. Expansion drops the node's original edges, so the
20
+ * template re-creates the `success` edge and, when the author wired `.onError`,
21
+ * an error boundary with the `error` edge.
22
+ *
23
+ * The input NAMES are the contract with the runtime half of the package: a
24
+ * handler reads them off `TaskContext.inputs`. A `string` context entry is
25
+ * emitted as `value` and a `json` one as a JSON `body`; the converter turns a
26
+ * falsy `string` into `""` and a falsy `json` into `{}`, so a package encodes
27
+ * booleans and numbers as `=js:` expressions rather than relying on either.
28
+ */
29
+ /** One authored input of a package node. */
30
+ export interface PackageInputField {
31
+ readonly type: 'string' | 'boolean' | 'number' | 'array' | 'object';
32
+ readonly description: string;
33
+ readonly required?: boolean;
34
+ readonly enum?: readonly string[];
35
+ readonly default?: unknown;
36
+ }
37
+ /** One field of a package node's `output`. */
38
+ export interface PackageOutputField {
39
+ readonly type: 'string' | 'boolean' | 'number' | 'array' | 'object';
40
+ readonly description: string;
41
+ }
42
+ /** What a package declares about one node; `defineNodeDefinition` does the rest. */
43
+ export interface PackageNodeShape {
44
+ /** The `.flow` node type, e.g. `acme.ledger.post`. */
45
+ readonly nodeType: string;
46
+ /** The BPMN service type a runtime dispatches on, e.g. `Acme.Ledger.Post`. */
47
+ readonly serviceType: string;
48
+ /** The definition version; the node's `typeVersion`. Default `1.0`. */
49
+ readonly version?: string;
50
+ readonly label: string;
51
+ readonly description: string;
52
+ /** A Lucide icon name the designer draws, e.g. `file-text`. */
53
+ readonly icon: string;
54
+ /** The designer's category for the add-node panel. Default `package`. */
55
+ readonly category?: string;
56
+ readonly tags?: readonly string[];
57
+ readonly inputs: Readonly<Record<string, PackageInputField>>;
58
+ readonly outputs: Readonly<Record<string, PackageOutputField>>;
59
+ }
60
+ export declare const DEFAULT_PACKAGE_DEFINITION_VERSION = "1.0";
61
+ /**
62
+ * The definition `defineNodeDefinition` returns: the complete manifest, with the
63
+ * fields a package's node type reads back typed — `version` is the type's
64
+ * `version` and the snapshot reference's, `model.serviceType` is what the runtime
65
+ * half claims.
66
+ */
67
+ export interface PackageNodeDefinition extends Record<string, unknown> {
68
+ readonly nodeType: string;
69
+ readonly version: string;
70
+ readonly model: Record<string, unknown> & {
71
+ readonly type: 'bpmn:ServiceTask';
72
+ readonly serviceType: string;
73
+ };
74
+ }
75
+ /**
76
+ * Build a package node's definition from its shape: the complete manifest a
77
+ * compiled flow carries as a `snapshot` reference, with the one-node expansion
78
+ * described above. Provenance (`source.package`) is stamped at serialize time
79
+ * from the family's `package`, not here, so a shape stays free of the package's
80
+ * own name.
81
+ */
82
+ export declare function defineNodeDefinition(shape: PackageNodeShape): PackageNodeDefinition;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * The node definition a node-type package writes into a compiled `.flow`.
3
+ *
4
+ * A package type has no platform definition — neither the SDK's bundled snapshot
5
+ * nor the tenant has one — so the file has to carry everything a reader needs:
6
+ * the designer's display and handles, the input and output schemas, and the
7
+ * `model.expansion` template the `.flow` → BPMN converter expands.
8
+ *
9
+ * ## Why one-node expansion
10
+ *
11
+ * The product's converter (vendored in `local-engine`) resolves a node's
12
+ * definition as `manifestDef ?? fileDef` and reads a node's per-instance input
13
+ * VALUES only through a definition's `model.expansion`: a `context` entry's
14
+ * `source: 'node.data.inputs.<name>'` is read off each placed node while the
15
+ * template is expanded. So every package definition expands to exactly one
16
+ * `bpmn:ServiceTask` that REPLACES the node under its own id, carries
17
+ * `<uipath:type value="<serviceType>">`, its inputs as context, and its
18
+ * `=response` / `=Error` output rows — the element shape a runtime's dispatch
19
+ * lane reads for a connector. Expansion drops the node's original edges, so the
20
+ * template re-creates the `success` edge and, when the author wired `.onError`,
21
+ * an error boundary with the `error` edge.
22
+ *
23
+ * The input NAMES are the contract with the runtime half of the package: a
24
+ * handler reads them off `TaskContext.inputs`. A `string` context entry is
25
+ * emitted as `value` and a `json` one as a JSON `body`; the converter turns a
26
+ * falsy `string` into `""` and a falsy `json` into `{}`, so a package encodes
27
+ * booleans and numbers as `=js:` expressions rather than relying on either.
28
+ */
29
+ export const DEFAULT_PACKAGE_DEFINITION_VERSION = '1.0';
30
+ const ERROR_SCHEMA = {
31
+ $schema: 'http://json-schema.org/draft-07/schema#',
32
+ type: 'object',
33
+ required: ['code', 'message', 'detail', 'category', 'status'],
34
+ properties: {
35
+ code: { type: 'string', description: 'Error code as a string' },
36
+ message: { type: 'string', description: 'High-level error message' },
37
+ detail: { type: 'string', description: 'Detailed error description' },
38
+ category: { type: 'string', description: 'Error category' },
39
+ status: { type: 'integer', description: 'HTTP status code' },
40
+ },
41
+ additionalProperties: false,
42
+ };
43
+ /** The context entry type the converter emits for a field: structured values ride as a JSON body. */
44
+ function contextType(field) {
45
+ return field.type === 'array' || field.type === 'object' ? 'json' : 'string';
46
+ }
47
+ /**
48
+ * Build a package node's definition from its shape: the complete manifest a
49
+ * compiled flow carries as a `snapshot` reference, with the one-node expansion
50
+ * described above. Provenance (`source.package`) is stamped at serialize time
51
+ * from the family's `package`, not here, so a shape stays free of the package's
52
+ * own name.
53
+ */
54
+ export function defineNodeDefinition(shape) {
55
+ const version = shape.version ?? DEFAULT_PACKAGE_DEFINITION_VERSION;
56
+ const context = Object.entries(shape.inputs).map(([name, field]) => ({
57
+ name,
58
+ type: contextType(field),
59
+ source: `node.data.inputs.${name}`,
60
+ }));
61
+ const properties = Object.fromEntries(Object.entries(shape.inputs).map(([name, field]) => [name, {
62
+ type: field.type,
63
+ description: field.description,
64
+ ...(field.enum ? { enum: [...field.enum] } : {}),
65
+ }]));
66
+ const required = Object.entries(shape.inputs).filter(([, f]) => f.required).map(([name]) => name);
67
+ const inputDefaults = Object.fromEntries(Object.entries(shape.inputs).filter(([, f]) => f.default !== undefined).map(([name, f]) => [name, f.default]));
68
+ return {
69
+ nodeType: shape.nodeType,
70
+ version,
71
+ category: shape.category ?? 'package',
72
+ description: shape.description,
73
+ tags: shape.tags ? [...shape.tags] : ['package', ...shape.nodeType.split('.').slice(1)],
74
+ sortOrder: 90,
75
+ supportsErrorHandling: true,
76
+ display: { label: shape.label, icon: shape.icon },
77
+ handleConfiguration: [
78
+ { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
79
+ {
80
+ position: 'right',
81
+ handles: [
82
+ { id: 'success', type: 'source', handleType: 'output' },
83
+ {
84
+ id: 'error', label: 'Error', type: 'source', handleType: 'output',
85
+ visible: '{inputs.errorHandlingEnabled}', constraints: { maxConnections: 1 },
86
+ },
87
+ ],
88
+ },
89
+ ],
90
+ debug: { runtime: 'bpmnEngine' },
91
+ model: {
92
+ type: 'bpmn:ServiceTask',
93
+ serviceType: shape.serviceType,
94
+ version: 'v1',
95
+ expansion: {
96
+ nodes: [
97
+ {
98
+ id: '{nodeId}',
99
+ type: 'bpmn:ServiceTask',
100
+ replaceOriginal: true,
101
+ position: { x: 0, y: 0 },
102
+ data: {
103
+ label: '{node.data.label}',
104
+ model: {
105
+ serviceType: shape.serviceType,
106
+ version: 'v1',
107
+ context,
108
+ outputs: [
109
+ { name: 'output', type: 'object', source: '=response', var: 'output' },
110
+ { name: 'error', type: 'object', source: '=Error', var: 'error' },
111
+ ],
112
+ },
113
+ },
114
+ },
115
+ {
116
+ condition: "hasEdgeFromHandle('error')",
117
+ id: '_Implicit_BoundaryError_{nodeId}',
118
+ type: 'bpmn:BoundaryEvent',
119
+ parentId: null,
120
+ position: { x: 0, y: 0 },
121
+ data: {
122
+ label: 'Error',
123
+ attachedToId: '{nodeId}',
124
+ eventDefinition: { type: 'bpmn:ErrorEventDefinition', id: '_Implicit_BoundaryError_{nodeId}_Error' },
125
+ // The `=Error` envelope is written by the BOUNDARY's output row when it
126
+ // fires, not by the failed task's own row — without this, `.onError`
127
+ // handlers read `<step>.error` as undefined. `custom: true` keeps the var
128
+ // as written: the converter would otherwise prefix it with the boundary's id.
129
+ model: { outputs: [{ name: 'error', type: 'object', source: '=Error', var: '{nodeId}.error', custom: true }] },
130
+ },
131
+ },
132
+ ],
133
+ edges: [
134
+ { matchOriginalEdgeByHandle: 'success', preserveEdgeId: true, source: '{nodeId}', type: 'bpmn:SequenceFlow' },
135
+ {
136
+ condition: "hasEdgeFromHandle('error')",
137
+ matchOriginalEdgeByHandle: 'error',
138
+ preserveEdgeId: true,
139
+ source: '_Implicit_BoundaryError_{nodeId}',
140
+ type: 'bpmn:SequenceFlow',
141
+ },
142
+ ],
143
+ },
144
+ },
145
+ inputDefinition: { type: 'object', properties, required },
146
+ inputDefaults,
147
+ outputDefinition: {
148
+ output: {
149
+ type: 'object',
150
+ description: `What ${shape.label} produced`,
151
+ source: '=response',
152
+ var: 'output',
153
+ schema: {
154
+ $schema: 'http://json-schema.org/draft-07/schema#',
155
+ type: 'object',
156
+ properties: Object.fromEntries(Object.entries(shape.outputs).map(([k, v]) => [k, { type: v.type, description: v.description }])),
157
+ },
158
+ },
159
+ error: { type: 'object', description: 'Error information if the step fails', source: '=Error', var: 'error', schema: ERROR_SCHEMA },
160
+ },
161
+ };
162
+ }
@@ -35,13 +35,25 @@ export interface NodeTypeRegistryOptions {
35
35
  export declare class NodeTypeRegistry<Ctx extends ArtifactContexts> {
36
36
  #private;
37
37
  readonly artifact: string;
38
- readonly families: readonly NodeFamily<Ctx>[];
39
38
  /**
40
39
  * @param artifact - The artifact's name, for messages (`'flow'`).
41
- * @param families - Every family this artifact supports, in no significant order.
40
+ * @param families - Every built-in family this artifact supports, in no significant order.
42
41
  * @param options - What the artifact knows that the contract does not.
43
42
  */
44
43
  constructor(artifact: string, families: readonly NodeFamily<Ctx>[], options: NodeTypeRegistryOptions);
44
+ /** Every family the registry knows: the built-in ones, then the registered packages' in registration order. */
45
+ get families(): readonly NodeFamily<Ctx>[];
46
+ /**
47
+ * Register a node-type package's family (`@uipath/maestro-builder-sdk/extend`).
48
+ *
49
+ * The whole registry is re-indexed with the new family last, under the same
50
+ * rules construction applies, so a package cannot take a kind, a type name, a
51
+ * prefix or a diagnostic code a built-in family owns, nor one another package
52
+ * registered first. A refusal names the package and leaves the live index as
53
+ * it was. Registering the same family object twice is a no-op: an ESM module
54
+ * runs once per process, but a test may import a package through two paths.
55
+ */
56
+ register(family: NodeFamily<Ctx>): void;
45
57
  /**
46
58
  * The node type for a spec, by its kind, or undefined when the kind is not
47
59
  * registered. When several types share the kind, the one whose `matches(spec)`