@uipath/maestro-builder-sdk 6.47.3 → 6.49.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.
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Diagram interchange (`bpmndi`) is intentionally **not** emitted here: BPMN
19
19
  * validation is layout-independent, so the SDK emits semantic-only XML and a
20
- * separate "tidy" step lays it out. See serialize.ts.
20
+ * `uip maestro bpmn format` lays it out. See serialize.ts.
21
21
  */
22
22
  import type { TypeDesc } from '../core/expr.js';
23
23
  import { type ConnectorOpts, type EventFilter } from '../core/actions.js';
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * Runs the fast static check first; if it passes, serializes to Maestro `.bpmn`
15
15
  * XML (semantic-only — no diagram). The authoritative deploy gate is
16
- * `uip maestro bpmn validate`; a separate "tidy" step lays the diagram out.
16
+ * `uip maestro bpmn validate`; `uip maestro bpmn format` lays the diagram out.
17
17
  */
18
18
  import { reportDiagnostics, reportResult, thrownDiagnostic } from '../cli-result.js';
19
19
  import { writeFileSync } from 'node:fs';
@@ -22,8 +22,8 @@
22
22
  * repo-relative for in-tree callers, mirroring the Flow and Case arms.
23
23
  *
24
24
  * Diagram layout is not read or emitted — the SDK emits semantic-only XML and
25
- * `tidy` regenerates `bpmndi`. Run `tidy` on the recompiled artifact when a canvas
26
- * needs one.
25
+ * `uip maestro bpmn format` regenerates `bpmndi`. Run it on the recompiled artifact
26
+ * when a canvas needs one.
27
27
  *
28
28
  * `--style nested` lifts the regions the builder's nesting constructs lower to —
29
29
  * see `BpmnDecompileOptions.style`. It is written only after both sources have been
@@ -16,9 +16,9 @@
16
16
  *
17
17
  * What does NOT survive, and why:
18
18
  *
19
- * - **`bpmndi` diagram layout.** The SDK emits none and `tidy` regenerates it, so
20
- * there is nothing to preserve. A decompiled file recompiles to semantic-only
21
- * XML; re-run `tidy` for a canvas.
19
+ * - **`bpmndi` diagram layout.** The SDK emits none and `uip maestro bpmn format`
20
+ * regenerates it, so there is nothing to preserve. A decompiled file recompiles
21
+ * to semantic-only XML; re-run `format` for a canvas.
22
22
  * - **Comments, method order, and formatting.** Regenerated, not preserved.
23
23
  *
24
24
  * Everything else round-trips: events and their definitions, all four gateways,
@@ -274,7 +274,7 @@ export function mergeBpmn(originalXml, editedXml, options = {}) {
274
274
  originalXml.slice(originalProcess.innerEnd);
275
275
  // The diagram: the original's layout survives, which is strictly better than the
276
276
  // re-layout a bare recompile forces. Shapes and edges for deleted elements go with
277
- // them; anything newly added has no DI yet, which is what `format`/`tidy` is for.
277
+ // them; anything newly added has no DI yet, which is what `format` is for.
278
278
  if (removed.length > 0) {
279
279
  const plane = findByLocal(parseXmlRanges(merged), 'BPMNPlane');
280
280
  if (plane) {
@@ -12,8 +12,7 @@ export declare function serialize(built: BuiltBpmn, opts?: BpmnSerializeOpts): P
12
12
  * an attribute whose value equals the BPMN default, but Studio Web (and the
13
13
  * canvas graders) expect an interrupting boundary event to carry an explicit
14
14
  * `cancelActivity="true"`. Add it wherever a boundary event lacks the attribute
15
- * (its absence already means interrupting). Idempotent; also used by `tidy`,
16
- * which re-serializes through moddle and drops the attribute again.
15
+ * (its absence already means interrupting). Idempotent.
17
16
  *
18
17
  * **This one is a KNOWN divergence from what the product writes, kept on purpose.**
19
18
  * None of the 71 boundary events across the five real Studio Web exports in
@@ -10,8 +10,8 @@
10
10
  * descriptor.
11
11
  *
12
12
  * **Semantic-only, no diagram.** BPMN validation is layout-independent, so no
13
- * `bpmndi:BPMNDiagram` is emitted here; a separate "tidy" step (auto-layout)
14
- * adds geometry when a visual diagram is needed.
13
+ * `bpmndi:BPMNDiagram` is emitted here; `uip maestro bpmn format` adds geometry
14
+ * when a visual diagram is needed.
15
15
  */
16
16
  import BpmnModdle from 'bpmn-moddle';
17
17
  import { createRequire } from 'node:module';
@@ -170,8 +170,7 @@ export async function serialize(built, opts = {}) {
170
170
  * an attribute whose value equals the BPMN default, but Studio Web (and the
171
171
  * canvas graders) expect an interrupting boundary event to carry an explicit
172
172
  * `cancelActivity="true"`. Add it wherever a boundary event lacks the attribute
173
- * (its absence already means interrupting). Idempotent; also used by `tidy`,
174
- * which re-serializes through moddle and drops the attribute again.
173
+ * (its absence already means interrupting). Idempotent.
175
174
  *
176
175
  * **This one is a KNOWN divergence from what the product writes, kept on purpose.**
177
176
  * None of the 71 boundary events across the five real Studio Web exports in
package/dist/check.js CHANGED
@@ -5,11 +5,11 @@
5
5
  * duplicate step names, references to undeclared variables/steps, empty branch
6
6
  * arms, and missing terminals.
7
7
  */
8
- import { Expr, SCHEDULE_PRESETS, parseIxpProjectType, parallelFork } from './flow-sdk.js';
8
+ import { Expr, SCHEDULE_PRESETS, parallelFork } from './flow-sdk.js';
9
9
  import { FlowNode } from './core/node-classes.js';
10
10
  import { planContributionBindings } from './core/contribution-bindings.js';
11
11
  import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
12
- import { bundledActionNodeType, bundledContainerDefinition, bundledDefinition, perCallDefinition, resolveContributionDefinition } from './serialize.js';
12
+ import { bundledActionNodeType, bundledContainerDefinition, bundledDefinition, resolveContributionDefinition } from './serialize.js';
13
13
  import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
14
14
  import { checkCookedEscapes } from './flow-expr-check.js';
15
15
  import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
@@ -528,8 +528,6 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
528
528
  outputReaders.set(stepName, (field, rest, at, d) => rule(stepName, field, rest, inputs, at, d));
529
529
  }
530
530
  }
531
- if (s.spec.kind === 'ixpExtract')
532
- checkIxpExtract(s.name, s.spec.inputs, diags);
533
531
  if (s.spec.kind === 'waitEvent') {
534
532
  const stepName = s.name;
535
533
  const push = (level) => (code, message, suggestion) => diags.push({
@@ -2194,76 +2192,6 @@ const OUTPUT_READ_RULES = {
2194
2192
  + 'its own false-positive sweep — flow-builder-sdk#855.',
2195
2193
  },
2196
2194
  };
2197
- /**
2198
- * The five forbidden inputs on an IxP node. They belonged to a prior schema and
2199
- * v1's own authoring reference calls them "the most common training-data-recall
2200
- * mistake" — the current node declares none of them, so each is an input the
2201
- * definition does not have.
2202
- *
2203
- * They are not reachable through `ixpExtract()`'s typed surface, which is the
2204
- * point of having one; this check exists for the JS caller and for a
2205
- * hand-written spec object, where TypeScript is not in the way.
2206
- */
2207
- const IXP_FORBIDDEN_INPUTS = ['digitizationMode', 'documentTaxonomy', 'attachmentId', 'fileName', 'mimeType'];
2208
- /**
2209
- * `ixpExtract(...)`-specific checks.
2210
- *
2211
- * All four identity fields are load-bearing on the tenant and none of them can
2212
- * be derived, so a missing one is an error rather than a warning: the node type
2213
- * is per-project, and the definition's own `inputDefinition` marks `modelName`
2214
- * and `folderKey` required (`minLength: 1`) beside `fileRef` — which is why
2215
- * `flow validate`'s `ixp-node` validator rejects a node with an empty one.
2216
- */
2217
- function checkIxpExtract(step, inputs, diags) {
2218
- const at = (code, message, suggestion) => {
2219
- diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
2220
- };
2221
- const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
2222
- const project = String(inputs?.project ?? '').trim();
2223
- if (blank(project)) {
2224
- at('IXP_NO_PROJECT', `IxP step "${step}" has no \`project\`. That is the extractor's whole node type, and it carries the `
2225
- + `project version id and folder key the platform resolves the deployment by. Copy it from the registry `
2226
- + `— never construct it: three different spellings of its tail exist in deployed flows.`, `project: 'uipath.ixp.<slug>.<projectVersionId>-<folderKey>' // uip maestro flow registry pull --force && uip maestro flow registry search -f "type:startsWith=uipath.ixp"`);
2227
- }
2228
- else if (!project.startsWith('uipath.ixp.')) {
2229
- at('IXP_PROJECT_NOT_NODE_TYPE', `IxP step "${step}"'s \`project\` is ${JSON.stringify(inputs.project)}, which is not a \`uipath.ixp.*\` node `
2230
- + `type. This field takes the type verbatim from \`registry search\`, not the project's name or id — the `
2231
- + `name goes in \`name\` and the slug in \`modelName\`.`);
2232
- }
2233
- else if (parseIxpProjectType(project) === undefined) {
2234
- at('IXP_PROJECT_TAIL_UNPARSEABLE', `IxP step "${step}"'s \`project\` ends in ${JSON.stringify(project.slice(project.lastIndexOf('.') + 1))}, which is `
2235
- + `not the \`<projectVersionId>-<folderKey>\` pair of uuids the current registry serves. Two older spellings `
2236
- + `exist in the corpus — a sanitized folder path, and the literal string "undefined" — and neither resolves `
2237
- + `today. Re-read the type from \`registry search\` after \`registry pull --force\`.`);
2238
- }
2239
- if (blank(inputs?.modelName)) {
2240
- at('IXP_NO_MODEL_NAME', `IxP step "${step}" has no \`modelName\`. The definition marks it required (\`minLength: 1\`) and the `
2241
- + `platform's \`ixp-node\` validator refuses an empty one — it is the project's own name as the tenant `
2242
- + `spells it, which the node type's slug has lower-cased and de-punctuated beyond recovery.`, `modelName: 'invoiceixp-cef0d447-ixp' // registry get → inputDefaults.modelName`);
2243
- }
2244
- if (blank(inputs?.name)) {
2245
- at('IXP_NO_NAME', `IxP step "${step}" has no \`name\`. It is the deployment's display name — the \`ixpDeployment\` binding's `
2246
- + `\`name\`, which is how the deployed flow finds the project.`, `name: 'InvoiceIXP' // registry get → inputDefaults.model.modelDisplayName`);
2247
- }
2248
- if (blank(inputs?.folderName)) {
2249
- at('IXP_NO_FOLDER', `IxP step "${step}" has no \`folderName\`. IxP deployments are folder-scoped, and this is the binding's `
2250
- + `\`folderPath\` default — the folder the runtime looks in.`, `folderName: 'Shared' // registry get → inputDefaults.folderName`);
2251
- }
2252
- const fileRef = inputs?.fileRef;
2253
- if (fileRef === undefined || fileRef === null
2254
- || (typeof fileRef === 'string' && fileRef.trim() === '')) {
2255
- at('IXP_NO_FILE', `IxP step "${step}" has no \`fileRef\`. It is the node's only required INPUT (\`inputDefinition.required\`) `
2256
- + `and it takes a whole Flow Attachment object — normally a \`file\`-typed flow input.`, `fileRef: out('start', 'invoiceFile') // .input({ invoiceFile: types.file })`);
2257
- }
2258
- for (const f of IXP_FORBIDDEN_INPUTS) {
2259
- if (inputs?.[f] !== undefined) {
2260
- at('IXP_FORBIDDEN_INPUT', `IxP step "${step}" sets \`${f}\`, which the current node does not declare — it belonged to a prior `
2261
- + `schema. ${f === 'documentTaxonomy' ? 'The taxonomy travels in the resolved `model` blob.'
2262
- : f === 'digitizationMode' ? 'The serializer defaults it to `fileUpload` internally.'
2263
- : 'It is derived from `fileRef` upstream.'}`);
2264
- }
2265
- }
2266
- }
2267
2195
  /** Best guess at what the author meant, for a `SCHEDULE_NOT_ISO` suggestion. */
2268
2196
  function nearestInterval(every) {
2269
2197
  // By far the likeliest slip: a correct duration missing the "R/" repeat marker.
@@ -3093,30 +3021,11 @@ placed, carried) {
3093
3021
  const bundledDef = bundledType
3094
3022
  ? bundledDefinition(bundledType, pinned) ?? (pinned !== undefined ? carried?.(bundledType, pinned) : undefined)
3095
3023
  : undefined;
3096
- // A per-tenant family: ask the definition serialize synthesizes for it (#1040).
3097
- let perCallDef;
3098
- if (!bundledType) {
3099
- try {
3100
- perCallDef = perCallDefinition(spec);
3101
- }
3102
- catch {
3103
- // The family's own checks report a spec its builder cannot lower.
3104
- return;
3105
- }
3106
- }
3107
- const def = bundledType ? bundledDef : perCallDef;
3108
- const noPort = def !== undefined
3109
- ? !definitionHasErrorPort(def)
3024
+ // Every per-tenant family is registered and answers above (its errorPort, or
3025
+ // a carried definition); what is left is bundled, or has no definition here.
3026
+ const noPort = bundledDef !== undefined
3027
+ ? !definitionHasErrorPort(bundledDef)
3110
3028
  : !bundledType && !NO_DEFINITION_ERROR_PORT_KINDS.has(spec.kind);
3111
- if (noPort && perCallDef !== undefined) {
3112
- diags.push({
3113
- level: 'error',
3114
- code: 'ONERROR_NO_ERROR_PORT',
3115
- message: errorPortRefusal(step.name, perCallDef, { bundled: true }),
3116
- step: step.name,
3117
- });
3118
- return;
3119
- }
3120
3029
  if (noPort) {
3121
3030
  diags.push({
3122
3031
  level: 'error',
@@ -53,15 +53,6 @@ export function registerBpmnCommands(program) {
53
53
  addValue(argv, '--bindings', options.bindings);
54
54
  await invoke('bpmn check', () => import('../../bpmn/check-cli.js'), argv);
55
55
  });
56
- family.command('tidy')
57
- .description('Add diagram layout to a compiled .bpmn artifact.')
58
- .argument('<input>', 'input .bpmn path')
59
- .option('-o, --output <file>', 'output .bpmn path')
60
- .action(async (input, options) => {
61
- const argv = [input];
62
- addValue(argv, '-o', options.output);
63
- await invoke('bpmn tidy', () => import('../../bpmn/tidy-cli.js'), argv);
64
- });
65
56
  family
66
57
  .command('decompile')
67
58
  .description('Convert a .bpmn artifact to authored TypeScript.')
@@ -257,98 +257,6 @@ export interface InOutField extends FormFieldSettings {
257
257
  required?: boolean;
258
258
  }
259
259
  export type FormField = ShownField | AskedField | InOutField;
260
- /**
261
- * Split an extractor's node type into the two GUIDs its tail packs.
262
- *
263
- * `uipath.ixp.<projectSlug>.<projectVersionId>-<folderKey>` — the version id is
264
- * the `ixpDeployment` binding's `resourceKey` and the node's `model.id`; the
265
- * folder key is `inputs.folderKey`. Returns `undefined` for a tail that is not
266
- * exactly two uuids joined by `-`, which is how the two older spellings (a
267
- * sanitized folder path, and the literal string `undefined`) are refused rather
268
- * than mis-split.
269
- *
270
- * @internal Public for compatibility; not an authoring factory.
271
- */
272
- export declare function parseIxpProjectType(nodeType: string): {
273
- slug: string;
274
- versionId: string;
275
- folderKey: string;
276
- } | undefined;
277
- export interface IxpExtractInputs {
278
- /**
279
- * The extractor's node type, verbatim from
280
- * `uip maestro flow registry search -f "type:startsWith=uipath.ixp"` — e.g.
281
- * `'uipath.ixp.invoiceixp-cef0d447-ixp.ff973488-5d89-8035-86ac-f980b4be6deb-c4359cde-55f0-4f0e-9322-c6cdce74ab4c'`.
282
- *
283
- * **Run `uip maestro flow registry pull --force` first.** `registry search`
284
- * and `registry get` answer from a local cache (`~/.uipath/nodes/`), so
285
- * without a pull you are reading whenever you last synced.
286
- *
287
- * Two GUIDs are packed in that tail — the project **version** id and the
288
- * **folder** key — and both are read back out of it here, which is why
289
- * nothing else has to be passed twice.
290
- */
291
- project: string;
292
- /**
293
- * The project's own name, as the tenant spells it — the slug, e.g.
294
- * `'idp-benchmark---invoices-c735405a-ixp'`. It is
295
- * `inputDefaults.modelName` in `registry get`, and the node carries it as
296
- * both `modelName` and `projectName` (one value, two fields — the designer
297
- * shows it in two disabled boxes).
298
- *
299
- * It cannot be recovered from {@link IxpExtractInputs.project}: the node type's slug is
300
- * lower-cased and de-punctuated (`---` collapses to `-`), which does not
301
- * invert.
302
- */
303
- modelName: string;
304
- /**
305
- * The deployment's display name, e.g. `'InvoiceIXP'` —
306
- * `inputDefaults.model.modelDisplayName`. It becomes the node's
307
- * `model.modelDisplayName` and the `ixpDeployment` binding's `name`.
308
- */
309
- name: string;
310
- /**
311
- * The folder the project is published in, e.g. `'Shared'` or
312
- * `'Shared/uipath-maestro-flow/BillingDispute'` —
313
- * `inputDefaults.folderName`. It becomes the binding's `folderPath` default
314
- * and the definition's own `description`.
315
- */
316
- folderName: string;
317
- /**
318
- * The document to extract from — a whole **Flow Attachment** object, the same
319
- * shape and the same two ways of referencing it as
320
- * {@link SummarizeInputs.attachment}:
321
- *
322
- * ```ts
323
- * flow('InvoiceIntake')
324
- * .input({ invoiceFile: types.file })
325
- * .step('extract', ixpExtract({ …, fileRef: out('start', 'invoiceFile') }))
326
- * ```
327
- *
328
- * It is the node's only authored input, and the definition marks it
329
- * `required` (with `modelName` and `folderKey`, which this factory fills in).
330
- */
331
- fileRef: Expr;
332
- /** Pages to process, e.g. `'1-5'`. Empty (the default) means the whole document. */
333
- pageRange?: string;
334
- /** Pin a published version tag. Empty (the default) means the latest. */
335
- versionTag?: string;
336
- /**
337
- * The folder's key CHAIN, when the project lives in a nested folder —
338
- * `inputDefaults.model.folderPath`, a dot-joined path of ancestor folder keys
339
- * (`<root>.<parent>.<folder>`). Defaults to the folder key alone, which is
340
- * what the platform writes for a project published straight into `Shared`.
341
- */
342
- folderPath?: string;
343
- /**
344
- * A free-text note on the node. Off by default, deliberately: the definition
345
- * does not declare it (so setting it draws a "sets an input its type does not
346
- * declare" warning unless the emitted definition is widened in the same
347
- * write, which this does), and it is empty in 114 of the 117 deployed nodes
348
- * that carry it at all.
349
- */
350
- description?: string;
351
- }
352
260
  /**
353
261
  * Where a process resource lives: deployed to an Orchestrator folder
354
262
  * (`'published'`, the default) or a sibling project of the SAME solution
@@ -914,10 +822,6 @@ export interface ContextIndexRef extends AgentResourcePlacement {
914
822
  * whole body rather than an inputs payload.
915
823
  */
916
824
  export interface FlowActionSpecs {
917
- /** The spec {@link ixpExtract} builds. */
918
- ixpExtract: {
919
- inputs: IxpExtractInputs;
920
- };
921
825
  /** The spec {@link connector} builds. */
922
826
  connector: {
923
827
  /** The connector key, e.g. `'uipath-salesforce-slack'`. */
@@ -977,61 +881,6 @@ export declare function builtinActionSpec<S extends object>(spec: S): S;
977
881
  * @returns The same spec, as a `FlowTrigger` instance.
978
882
  */
979
883
  export declare function builtinTriggerSpec<S extends object>(spec: S): S;
980
- /**
981
- * Extract fields from a document with a published IxP project — the platform's
982
- * **Extract** node (`uipath.ixp.*`, service contract `IXP.Extraction`).
983
- *
984
- * @remarks
985
- * **The result's shape is three levels deep and PascalCase.** A field lands at
986
- * `out('<step>', 'ExtractionResult.ResultsDocument.Fields')` — an array of
987
- * `{ FieldId, FieldName, FieldType, IsMissing, Values[], Confidence }`, looked
988
- * up by `FieldName`. `Fields[]` is under `ResultsDocument`, not directly under
989
- * `ExtractionResult`, and there is no `result` wrapper: the platform's
990
- * serializer strips it before the value reaches the variable.
991
- *
992
- * **A local run cannot extract anything** — extraction needs a real document on
993
- * the IxP service. An offline run returns the baked
994
- * fixture shape, and only a platform run reaches the model. So a green local
995
- * rung proves the wiring and the definition, never that a document was read.
996
- *
997
- * **`fileRef` is emitted as a PLAIN `=js:` string, not the expression envelope
998
- * the rest of the file uses** — the one place this family diverges from the
999
- * format's general spelling, and the contract this node owes the platform. The
1000
- * product's own `ixp-node` validator tests `typeof fileRef !== 'string'` and
1001
- * refuses the object form (*"inputs.fileRef must be a `=js:$vars.…`
1002
- * expression"*), the node's registry manifest declares
1003
- * `{type:'string', minLength:1}`, and so do all 150 v1-authored instances in the
1004
- * deployed corpus that carry the field — at file versions 1.6, 1.8 and 1.9 alike,
1005
- * with zero envelopes among them. The RUNTIME
1006
- * accepts both, so only `uip maestro flow validate` sees a wrong one; the
1007
- * emitter's exemption lives in `migrate.ts` (`UNWRAPPED_INPUTS`).
1008
- * See the IxP node reference (`website/reference/ixp.md`) §3.
1009
- *
1010
- * @example
1011
- * **Extract an invoice total and read it out of the nested result**
1012
- * ```ts
1013
- * flow('InvoiceIntake')
1014
- * .input({ invoiceFile: types.file })
1015
- * .output({ total: types.string })
1016
- * .step('extract', ixpExtract({
1017
- * project: 'uipath.ixp.invoiceixp-cef0d447-ixp.ff973488-5d89-8035-86ac-f980b4be6deb-c4359cde-55f0-4f0e-9322-c6cdce74ab4c',
1018
- * modelName: 'invoiceixp-cef0d447-ixp',
1019
- * name: 'InvoiceIXP',
1020
- * folderName: 'Shared',
1021
- * fileRef: out('start', 'invoiceFile'),
1022
- * }))
1023
- * .step('readTotal', script({ code: `
1024
- * const fields = $vars.extract.output.ExtractionResult.ResultsDocument.Fields || [];
1025
- * return { total: fields.find(f => f.FieldName === 'invoiceTotal')?.Values?.[0] };
1026
- * ` }))
1027
- * .return({ total: out('readTotal', 'total') })
1028
- * ```
1029
- *
1030
- * @param inputs - The published IxP `project` and `modelName`, and the document
1031
- * to read (`fileRef`).
1032
- * @returns An action spec to pass to `.step(name, …)`.
1033
- */
1034
- export declare function ixpExtract(inputs: IxpExtractInputs): ActionSpec;
1035
884
  /**
1036
885
  * Declare an Integration Service connector action — the typed form, where a
1037
886
  * generated descriptor supplies the nodeType and statically-checked input types:
@@ -1,44 +1,5 @@
1
1
  import { splitConnectorNodeType, isTriggerDescriptor, } from './connectors.js';
2
2
  import { FlowAction, FlowTrigger } from './node-classes.js';
3
- // ─── Document extraction (uipath.ixp.*) ──────────────────────────────────────
4
- //
5
- // The platform's *Extract* node (that is its `display.canvasLabel`): it hands a
6
- // document to a published IxP — Intelligent eXtraction Platform — project and
7
- // reads the trained model's fields back. Every extractor on a tenant is its own
8
- // node type, `uipath.ixp.<projectSlug>.<projectVersionId>-<folderKey>`, so the
9
- // four identity fields below come from the tenant and are named exactly as the
10
- // node names them.
11
- //
12
- // The type is RESOLVED, never constructed: three different spellings of that
13
- // tail exist in the deployed corpus (the sanitized folder path, the literal
14
- // string `undefined`, and today's version-id + folder-key pair), so copy the
15
- // one `registry search` prints.
16
- /**
17
- * Split an extractor's node type into the two GUIDs its tail packs.
18
- *
19
- * `uipath.ixp.<projectSlug>.<projectVersionId>-<folderKey>` — the version id is
20
- * the `ixpDeployment` binding's `resourceKey` and the node's `model.id`; the
21
- * folder key is `inputs.folderKey`. Returns `undefined` for a tail that is not
22
- * exactly two uuids joined by `-`, which is how the two older spellings (a
23
- * sanitized folder path, and the literal string `undefined`) are refused rather
24
- * than mis-split.
25
- *
26
- * @internal Public for compatibility; not an authoring factory.
27
- */
28
- export function parseIxpProjectType(nodeType) {
29
- const rest = nodeType.startsWith('uipath.ixp.') ? nodeType.slice('uipath.ixp.'.length) : undefined;
30
- if (!rest)
31
- return undefined;
32
- const dot = rest.lastIndexOf('.');
33
- if (dot <= 0)
34
- return undefined;
35
- const slug = rest.slice(0, dot);
36
- const tail = rest.slice(dot + 1);
37
- const m = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(tail);
38
- if (!m)
39
- return undefined;
40
- return { slug, versionId: m[1], folderKey: m[2] };
41
- }
42
3
  // ── Built-in spec adapters ──────────────────────────────────────────────────
43
4
  // Every factory result IS a FlowAction/FlowTrigger instance (design §4.4 pass
44
5
  // 1), so `instanceof` can tell an SDK-owned construct from a raw literal. The
@@ -88,63 +49,6 @@ export function builtinActionSpec(spec) {
88
49
  export function builtinTriggerSpec(spec) {
89
50
  return new BuiltinTrigger(spec);
90
51
  }
91
- /**
92
- * Extract fields from a document with a published IxP project — the platform's
93
- * **Extract** node (`uipath.ixp.*`, service contract `IXP.Extraction`).
94
- *
95
- * @remarks
96
- * **The result's shape is three levels deep and PascalCase.** A field lands at
97
- * `out('<step>', 'ExtractionResult.ResultsDocument.Fields')` — an array of
98
- * `{ FieldId, FieldName, FieldType, IsMissing, Values[], Confidence }`, looked
99
- * up by `FieldName`. `Fields[]` is under `ResultsDocument`, not directly under
100
- * `ExtractionResult`, and there is no `result` wrapper: the platform's
101
- * serializer strips it before the value reaches the variable.
102
- *
103
- * **A local run cannot extract anything** — extraction needs a real document on
104
- * the IxP service. An offline run returns the baked
105
- * fixture shape, and only a platform run reaches the model. So a green local
106
- * rung proves the wiring and the definition, never that a document was read.
107
- *
108
- * **`fileRef` is emitted as a PLAIN `=js:` string, not the expression envelope
109
- * the rest of the file uses** — the one place this family diverges from the
110
- * format's general spelling, and the contract this node owes the platform. The
111
- * product's own `ixp-node` validator tests `typeof fileRef !== 'string'` and
112
- * refuses the object form (*"inputs.fileRef must be a `=js:$vars.…`
113
- * expression"*), the node's registry manifest declares
114
- * `{type:'string', minLength:1}`, and so do all 150 v1-authored instances in the
115
- * deployed corpus that carry the field — at file versions 1.6, 1.8 and 1.9 alike,
116
- * with zero envelopes among them. The RUNTIME
117
- * accepts both, so only `uip maestro flow validate` sees a wrong one; the
118
- * emitter's exemption lives in `migrate.ts` (`UNWRAPPED_INPUTS`).
119
- * See the IxP node reference (`website/reference/ixp.md`) §3.
120
- *
121
- * @example
122
- * **Extract an invoice total and read it out of the nested result**
123
- * ```ts
124
- * flow('InvoiceIntake')
125
- * .input({ invoiceFile: types.file })
126
- * .output({ total: types.string })
127
- * .step('extract', ixpExtract({
128
- * project: 'uipath.ixp.invoiceixp-cef0d447-ixp.ff973488-5d89-8035-86ac-f980b4be6deb-c4359cde-55f0-4f0e-9322-c6cdce74ab4c',
129
- * modelName: 'invoiceixp-cef0d447-ixp',
130
- * name: 'InvoiceIXP',
131
- * folderName: 'Shared',
132
- * fileRef: out('start', 'invoiceFile'),
133
- * }))
134
- * .step('readTotal', script({ code: `
135
- * const fields = $vars.extract.output.ExtractionResult.ResultsDocument.Fields || [];
136
- * return { total: fields.find(f => f.FieldName === 'invoiceTotal')?.Values?.[0] };
137
- * ` }))
138
- * .return({ total: out('readTotal', 'total') })
139
- * ```
140
- *
141
- * @param inputs - The published IxP `project` and `modelName`, and the document
142
- * to read (`fileRef`).
143
- * @returns An action spec to pass to `.step(name, …)`.
144
- */
145
- export function ixpExtract(inputs) {
146
- return builtinActionSpec({ kind: 'ixpExtract', inputs });
147
- }
148
52
  export function connector(a, b, c, d) {
149
53
  if (typeof a === 'string') {
150
54
  const opts = (d ?? {});
@@ -52,7 +52,8 @@ export declare class NodeTypeRegistry<Ctx extends ArtifactContexts> {
52
52
  typesOfKind(kind: string): readonly Registered<Ctx>[];
53
53
  /**
54
54
  * The node type for a type name in the file — the exact name, else the type
55
- * whose `typeNamePrefix` it starts with — or undefined when no type owns it.
55
+ * whose `typeNamePrefix` it starts with (and whose `typeNameMatches`, if any,
56
+ * accepts it) — or undefined when no type owns it.
56
57
  * The bare prefix counts: it is what an empty `key` builds, and the old lift
57
58
  * read it as its factory, so it still does.
58
59
  */
@@ -1,5 +1,9 @@
1
1
  import { asVersioned } from './versioned.js';
2
2
  import { ownEntry } from '../own-entry.js';
3
+ /** Whether a prefix entry owns `typeName`: under its prefix, and accepted by its narrowing, if it has one. */
4
+ function owns(p, typeName) {
5
+ return typeName.startsWith(p.prefix) && (p.matches?.(typeName) ?? true);
6
+ }
3
7
  export class NodeTypeRegistry {
4
8
  artifact;
5
9
  families;
@@ -44,15 +48,18 @@ export class NodeTypeRegistry {
44
48
  const overlap = this.#byPrefix.find((p) => p.prefix.startsWith(prefix) || prefix.startsWith(p.prefix));
45
49
  if (overlap)
46
50
  problems.push(`${where}: prefix "${prefix}" overlaps "${overlap.prefix}" (${placeOf(overlap.entry)})`);
47
- const shadowed = [...this.#byTypeName.keys()].find((n) => n.startsWith(prefix));
51
+ const shadowed = [...this.#byTypeName.keys()].find((n) => n.startsWith(prefix) && (type.typeNameMatches?.(n) ?? true));
48
52
  if (shadowed)
49
53
  problems.push(`${where}: prefix "${prefix}" would also own the registered type "${shadowed}"`);
50
54
  }
51
55
  else {
52
- const owner = this.#byPrefix.find((p) => type.typeName.startsWith(p.prefix));
56
+ const owner = this.#byPrefix.find((p) => owns(p, type.typeName));
53
57
  if (owner)
54
58
  problems.push(`${where}: type name "${type.typeName}" falls under the prefix "${owner.prefix}" (${placeOf(owner.entry)})`);
55
59
  }
60
+ if (type.typeNameMatches !== undefined && type.typeNamePrefix === undefined) {
61
+ problems.push(`${where}: typeNameMatches narrows a typeNamePrefix, and the type declares none`);
62
+ }
56
63
  const typeOwner = this.#byTypeName.get(type.typeName);
57
64
  if (typeOwner) {
58
65
  problems.push(`type name "${type.typeName}" is registered twice (${placeOf(typeOwner)} and ${where}); one VersionedNodeType owns every version of a type`);
@@ -70,7 +77,7 @@ export class NodeTypeRegistry {
70
77
  this.#byKind.set(type.kind, [...(sharing ?? []), entry]);
71
78
  this.#byTypeName.set(type.typeName, entry);
72
79
  if (type.typeNamePrefix !== undefined) {
73
- this.#byPrefix.push({ prefix: type.typeNamePrefix, entry });
80
+ this.#byPrefix.push({ prefix: type.typeNamePrefix, matches: type.typeNameMatches?.bind(type), entry });
74
81
  }
75
82
  }
76
83
  }
@@ -102,7 +109,8 @@ export class NodeTypeRegistry {
102
109
  }
103
110
  /**
104
111
  * The node type for a type name in the file — the exact name, else the type
105
- * whose `typeNamePrefix` it starts with — or undefined when no type owns it.
112
+ * whose `typeNamePrefix` it starts with (and whose `typeNameMatches`, if any,
113
+ * accepts it) — or undefined when no type owns it.
106
114
  * The bare prefix counts: it is what an empty `key` builds, and the old lift
107
115
  * read it as its factory, so it still does.
108
116
  */
@@ -110,7 +118,7 @@ export class NodeTypeRegistry {
110
118
  const exact = this.#byTypeName.get(typeName);
111
119
  if (exact)
112
120
  return exact;
113
- return this.#byPrefix.find((p) => typeName.startsWith(p.prefix))?.entry;
121
+ return this.#byPrefix.find((p) => owns(p, typeName))?.entry;
114
122
  }
115
123
  /** Every registered kind, sorted. */
116
124
  kinds() {
@@ -25,6 +25,14 @@ Impl extends NodeType<Spec, Serialized, Ctx> = NodeType<Spec, Serialized, Ctx>>
25
25
  * per-tenant: no definition could be bundled for a name the SDK cannot know.
26
26
  */
27
27
  readonly typeNamePrefix?: string;
28
+ /**
29
+ * Narrows {@link typeNamePrefix}: the type owns a name under the prefix only
30
+ * when this accepts it. For a family whose names share a stem with exact names
31
+ * of other types — an IxP extractor is `uipath.ixp.<slug>.<versionId>-<folderKey>`,
32
+ * beside `uipath.ixp.extract-document-builder` — the registry then lets an
33
+ * exact name sit under the prefix as long as this rejects it.
34
+ */
35
+ typeNameMatches?(typeName: string): boolean;
28
36
  readonly factory: string;
29
37
  /** The version used when the author names none. Changing it is a breaking change. */
30
38
  readonly defaultVersion: string;