@uipath/maestro-builder-sdk 6.11.5 → 6.12.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.
Files changed (56) hide show
  1. package/dist/api-index.md +83 -56
  2. package/dist/api-members.md +362 -261
  3. package/dist/bpmn/bpmn-expr-check.js +8 -1
  4. package/dist/bpmn/bpmn-sdk.d.ts +1026 -27
  5. package/dist/bpmn/bpmn-sdk.js +1328 -83
  6. package/dist/bpmn/check-cli.js +19 -4
  7. package/dist/bpmn/check.d.ts +1 -1
  8. package/dist/bpmn/check.js +529 -13
  9. package/dist/bpmn/connector.d.ts +2 -2
  10. package/dist/bpmn/connector.js +74 -37
  11. package/dist/bpmn/data-service.d.ts +124 -0
  12. package/dist/bpmn/data-service.js +98 -0
  13. package/dist/bpmn/decompile-cli.js +70 -8
  14. package/dist/bpmn/decompile.d.ts +71 -0
  15. package/dist/bpmn/decompile.js +845 -31
  16. package/dist/bpmn/graph.d.ts +69 -0
  17. package/dist/bpmn/graph.js +98 -0
  18. package/dist/bpmn/intsvc.js +2 -2
  19. package/dist/bpmn/known-methods.d.ts +15 -0
  20. package/dist/bpmn/known-methods.js +271 -0
  21. package/dist/bpmn/lift-verify.d.ts +40 -0
  22. package/dist/bpmn/lift-verify.js +133 -0
  23. package/dist/bpmn/serialize.d.ts +65 -1
  24. package/dist/bpmn/serialize.js +169 -22
  25. package/dist/bpmn/typed-node.d.ts +37 -6
  26. package/dist/bpmn/typed-node.js +81 -19
  27. package/dist/check-cli.js +2 -1
  28. package/dist/check.d.ts +13 -0
  29. package/dist/check.js +11 -0
  30. package/dist/cli/commands/bpmn.js +7 -1
  31. package/dist/cli-library.d.ts +7 -0
  32. package/dist/cli-library.js +19 -0
  33. package/dist/compile-cli.js +8 -5
  34. package/dist/core/byoa.d.ts +69 -0
  35. package/dist/core/byoa.js +98 -0
  36. package/dist/core/library.d.ts +23 -0
  37. package/dist/core/library.js +47 -1
  38. package/dist/core/lookup-store.d.ts +12 -0
  39. package/dist/core/lookup-store.js +25 -0
  40. package/dist/index.d.ts +5 -2
  41. package/dist/index.js +3 -0
  42. package/dist/registry/generators/descriptors.d.ts +1 -1
  43. package/dist/registry/generators/descriptors.js +20 -2
  44. package/dist/registry/generators/markdown/page.js +3 -1
  45. package/dist/registry/generators/markdown/sections/event-facts.d.ts +2 -0
  46. package/dist/registry/generators/markdown/sections/event-facts.js +45 -0
  47. package/dist/registry/library-build/events.d.ts +32 -3
  48. package/dist/registry/library-build/events.js +48 -19
  49. package/dist/registry/prepare/byoa-ledger.d.ts +12 -0
  50. package/dist/registry/prepare/byoa-ledger.js +26 -0
  51. package/dist/registry/prepare/event.d.ts +5 -1
  52. package/dist/registry/prepare/event.js +22 -8
  53. package/dist/registry/prepare/messages.d.ts +2 -0
  54. package/dist/registry/prepare/messages.js +4 -0
  55. package/dist/serialize.js +24 -1
  56. package/package.json +2 -2
@@ -19,12 +19,16 @@ export function registryTypeNames() {
19
19
  * `FLOW_SDK_BPMN_REGISTRY_FALLBACK=0` to keep resolution snapshot-only.
20
20
  *
21
21
  * @param type - The extension type, e.g. `'Intsvc.UnifiedHttpRequest'`.
22
+ * @param inline - A spec supplied by the author, which is used as is.
22
23
  * @returns Its emission spec.
23
24
  * @throws If the type is in neither the snapshot nor the registry — which means a
24
25
  * typo, or no `uip` to ask.
25
26
  */
26
- export function registryType(type) {
27
- const spec = REGISTRY.types[type] ?? resolveFromRegistry(type);
27
+ export function registryType(type, inline) {
28
+ // An inline spec wins over both the snapshot and the tenant — it is the author
29
+ // saying "emit THIS", for a type the snapshot lacks and no `uip` can describe, or
30
+ // for one the snapshot describes wrongly. See `ActivityNodeOpts.spec`.
31
+ const spec = inline ?? REGISTRY.types[type] ?? resolveFromRegistry(type);
28
32
  if (!spec) {
29
33
  throw new Error(`unknown extension type "${type}". The committed registry snapshot has ${REGISTRY.provenance.typeCount} types `
30
34
  + `(uip ${REGISTRY.provenance.uipVersion}) and the registry could not be asked for this one either — `
@@ -82,7 +86,22 @@ export const REGISTRY_GAPS = {
82
86
  // which `uip maestro bpmn validate` has always accepted. So the guard was right
83
87
  // that they are undeclared and wrong that they are invalid — recorded here rather
84
88
  // than by loosening the guard, which is what catches an author's typo.
85
- 'Intsvc.ActivityExecution': { extraContext: ['activity', 'resourceKey'] },
89
+ // `requiredContext` — context fields the PLATFORM requires and the registry
90
+ // snapshot does not mark required (or, here, does not declare at all).
91
+ //
92
+ // `Intsvc.ActivityExecution` must carry `activity`; without it `uip maestro bpmn
93
+ // validate` reports
94
+ //
95
+ // [canvas] [ERROR] EMPTY_REQUIRED_FIELD: The field 'activity' in node 'op' is
96
+ // required but has no value.
97
+ //
98
+ // and `check` could not know, because it derives required fields from the snapshot.
99
+ // The connector emitter has always written `activity`, so this only bites a
100
+ // hand-rolled `.activity()` — but it bit it at the LAST rung. The divergence is
101
+ // measured rather than assumed: `bpmn-registry-required-drift.test.ts` diffs the
102
+ // required-field set the canvas validator carries against the snapshot's, over every
103
+ // type both know, and asserts the only difference is what this table records.
104
+ 'Intsvc.ActivityExecution': { extraContext: ['activity', 'resourceKey'], requiredContext: ['activity'] },
86
105
  // `outputRowsFixed` — the type permits EXACTLY the output row its spec declares.
87
106
  // A mapped second row is refused:
88
107
  //
@@ -115,8 +134,8 @@ function identifierize(name) {
115
134
  .join('');
116
135
  }
117
136
  /** The variable a typed node's output row writes, or `undefined` when it has none. */
118
- export function typedNodeOutputVar(id, type, outputVar) {
119
- const spec = registryType(type);
137
+ export function typedNodeOutputVar(id, type, outputVar, inline) {
138
+ const spec = registryType(type, inline);
120
139
  if (!spec.output)
121
140
  return undefined;
122
141
  return outputVar ?? `${id}_${identifierize(spec.output.name)}`;
@@ -182,7 +201,13 @@ function contextRows(m, type, spec, supplied, verbatim = false) {
182
201
  // it and the author never learns why their setting did nothing — so it is
183
202
  // refused. The exception is a type with a recorded `extraContext` gap, where the
184
203
  // platform is known to accept fields the registry omits.
185
- const known = new Set([...spec.context.map((f) => f.name), ...(REGISTRY_GAPS[type]?.extraContext ?? [])]);
204
+ const known = new Set([
205
+ ...spec.context.map((f) => f.name),
206
+ ...(REGISTRY_GAPS[type]?.extraContext ?? []),
207
+ // A field the platform REQUIRES is necessarily one it permits, so recording it as
208
+ // required is enough — an `extraContext` twin is not also needed.
209
+ ...(REGISTRY_GAPS[type]?.requiredContext ?? []),
210
+ ]);
186
211
  const unknown = Object.keys(supplied).filter((k) => !known.has(k));
187
212
  if (unknown.length) {
188
213
  throw new Error(`context field(s) ${unknown.map((u) => `"${u}"`).join(', ')} are not declared by this extension type. ` +
@@ -234,10 +259,16 @@ function inputRows(m, spec, inputs) {
234
259
  // platform does not read, and one no validator complains about.
235
260
  //
236
261
  // So: the declared field decides when there is one, the key when there is not.
262
+ //
263
+ // The same split decides the TYPE of an undeclared field. Every undeclared value
264
+ // used to be written as a `json` body, so a scalar came out JSON-quoted —
265
+ // `recordId` as `"1111-…"`, quotes included, where every real export writes
266
+ // `type="string" value="1111-…"`. A structured value is still a json body; a
267
+ // scalar is written as the `value` attribute, the way a declared scalar is.
237
268
  const declared = new Map(spec.inputs.map((f) => [f.name, f]));
238
269
  return keys.map((k) => {
239
270
  const field = declared.get(k);
240
- return payloadInput(m, k, field?.target ?? k, inputs[k], field?.type ?? 'json');
271
+ return payloadInput(m, k, field?.target ?? k, inputs[k], field?.type ?? inferredInputType(inputs[k]));
241
272
  });
242
273
  }
243
274
  if (!spec.inputName)
@@ -277,6 +308,23 @@ function payloadInput(m, name, target, value, type) {
277
308
  props.target = target;
278
309
  return m.create('uipath:Input', props);
279
310
  }
311
+ /** `json` for an object or array, `string` for anything else — see `inputRows`. */
312
+ function inferredInputType(value) {
313
+ return value !== null && typeof value === 'object' ? 'json' : 'string';
314
+ }
315
+ /** `uipath:input` payload rows exactly as the caller spelled them, deriving nothing. */
316
+ export function verbatimInputRows(m, rows) {
317
+ return rows.map((row) => {
318
+ const props = { name: row.name, type: row.type ?? 'string' };
319
+ if (row.target !== undefined)
320
+ props.target = row.target;
321
+ if (row.value !== undefined)
322
+ props.value = row.value;
323
+ if (row.body !== undefined)
324
+ props.body = jsonBody(row.body);
325
+ return m.create('uipath:Input', props);
326
+ });
327
+ }
280
328
  /** The `uipath:output` row, when the type declares one. */
281
329
  function outputRow(m, spec, varName) {
282
330
  if (!spec.output)
@@ -308,7 +356,7 @@ export function createTypedNode(m, id, type, input,
308
356
  * Optional so a caller with no variable context still gets today's behaviour.
309
357
  */
310
358
  varType) {
311
- const spec = registryType(type);
359
+ const spec = registryType(type, input.spec);
312
360
  if (!spec.element || !spec.tag) {
313
361
  throw new Error(`extension type "${type}" has no element/tag in the registry snapshot; it cannot be authored.`);
314
362
  }
@@ -352,7 +400,10 @@ varType) {
352
400
  }
353
401
  payload.context = m.create('uipath:Context', context);
354
402
  }
355
- const ins = inputRows(m, spec, input.inputs ?? {});
403
+ // Spelled out by the caller: emit exactly these rows, deriving nothing — see
404
+ // TypedNodeInput.inputRows. This is what lets a connector's per-parameter rows
405
+ // (`name="start" type="string" value="0" target="query"`) survive a round trip.
406
+ const ins = input.inputRows ? verbatimInputRows(m, input.inputRows) : inputRows(m, spec, input.inputs ?? {});
356
407
  if (ins.length)
357
408
  payload.input = ins;
358
409
  const rows = [];
@@ -365,7 +416,7 @@ varType) {
365
416
  payload.output = rows;
366
417
  return finish(m, spec, id, input.name, payload);
367
418
  }
368
- const outVar = typedNodeOutputVar(id, type, input.outputVar);
419
+ const outVar = typedNodeOutputVar(id, type, input.outputVar, spec);
369
420
  const out = outVar ? outputRow(m, spec, outVar) : undefined;
370
421
  if (out)
371
422
  rows.push(out);
@@ -476,8 +527,8 @@ function moddleElementOf(element) {
476
527
  * @param type - The extension type.
477
528
  * @returns Its fields that must be bound, with the target each binding needs.
478
529
  */
479
- export function bindingRequiredFields(type) {
480
- return registryType(type).context.filter((f) => f.bindingInfo !== null && f.required);
530
+ export function bindingRequiredFields(type, inline) {
531
+ return registryType(type, inline).context.filter((f) => f.bindingInfo !== null && f.required);
481
532
  }
482
533
  /**
483
534
  * Required context fields the author left unset and the registry cannot default.
@@ -490,10 +541,21 @@ export function bindingRequiredFields(type) {
490
541
  * @param context - The context values the author supplied.
491
542
  * @returns The names of required fields with no value and no default.
492
543
  */
493
- export function missingRequiredContext(type, context = {}) {
494
- return registryType(type)
495
- .context.filter((f) => f.required && context[f.name] === undefined && (f.default ?? '') === '')
544
+ export function missingRequiredContext(type, context = {}, inline) {
545
+ // The platform's test is `isNilOrEmpty(field.value)`, so a field present and BLANK is
546
+ // as missing as one absent — and blank is the likelier authoring slip of the two, a
547
+ // value that came from an expression or a variable that had nothing in it. Checking
548
+ // only for `undefined` let every one of those through to the last rung.
549
+ const unset = (name) => {
550
+ const v = context[name];
551
+ return v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
552
+ };
553
+ const declared = registryType(type, inline)
554
+ .context.filter((f) => f.required && unset(f.name) && (f.default ?? '') === '')
496
555
  .map((f) => f.name);
556
+ // Fields the platform requires that the snapshot does not — see `REGISTRY_GAPS`.
557
+ const gaps = (REGISTRY_GAPS[type]?.requiredContext ?? []).filter((name) => !declared.includes(name) && unset(name));
558
+ return [...declared, ...gaps];
497
559
  }
498
560
  /**
499
561
  * Required PAYLOAD fields the author left unset.
@@ -519,9 +581,9 @@ export function missingRequiredInputs(type, inputs = {},
519
581
  * genuinely does not spell (it writes `connection`). Reading both makes this
520
582
  * agree with the CLI instead of guessing on either side of it.
521
583
  */
522
- context = {}) {
584
+ context = {}, inline) {
523
585
  const notEnforced = new Set(REGISTRY_GAPS[type]?.notEnforcedInputs ?? []);
524
- return registryType(type)
586
+ return registryType(type, inline)
525
587
  .inputs.filter((f) => f.required &&
526
588
  !notEnforced.has(f.name) &&
527
589
  inputs[f.name] === undefined &&
@@ -560,8 +622,8 @@ context = {}) {
560
622
  * @param type - The extension type.
561
623
  * @returns The required payload field names that cannot be expressed, or `[]`.
562
624
  */
563
- export function unsatisfiableRequiredInputs(type) {
564
- const spec = registryType(type);
625
+ export function unsatisfiableRequiredInputs(type, inline) {
626
+ const spec = registryType(type, inline);
565
627
  if (spec.inputPattern !== 'mergedBody' && spec.inputPattern !== 'splitInputsItemData')
566
628
  return [];
567
629
  return spec.inputs.filter((f) => f.required).map((f) => f.name);
package/dist/check-cli.js CHANGED
@@ -9,7 +9,7 @@ import { reportDiagnostics } from './cli-result.js';
9
9
  import { existsSync } from 'node:fs';
10
10
  import { resolveFlowFile, loadBuiltFlow } from './load.js';
11
11
  import { check } from './check.js';
12
- import { eventResolveErrorFor, openCliLibrary } from './cli-library.js';
12
+ import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
13
13
  import { Bindings } from './bindings.js';
14
14
  import { connectorInputErrors } from './serialize.js';
15
15
  import { runWhenInvokedDirectly } from './cli-run.js';
@@ -41,6 +41,7 @@ function checkOpts(argv) {
41
41
  return bindings ? { bindings } : {};
42
42
  return {
43
43
  eventResolveError: eventResolveErrorFor(library),
44
+ ...byoaCheckOpts(library),
44
45
  eventParams: (connector, event, object) => {
45
46
  try {
46
47
  return library.eventParameterFields(connector, event, object);
package/dist/check.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type LookupResolutions, type LookupSpec } from './core/lookups.js';
2
2
  import type { SchemaReadiness } from './core/library.js';
3
3
  import type { BuiltFlow } from './flow-sdk.js';
4
4
  import { type BindingsView } from './core/binding-messages.js';
5
+ import { type ByoaEventFacts, type ByoaLedger } from './core/byoa.js';
5
6
  export interface Diagnostic {
6
7
  level: 'error' | 'warning';
7
8
  code: string;
@@ -116,6 +117,18 @@ export interface CheckOpts {
116
117
  lookups?: (connector: string, action: string, version?: string, object?: string) => Readonly<Record<string, LookupSpec>> | undefined;
117
118
  /** Everything `registry prepare --resolve` has recorded in the local overlay. */
118
119
  resolutions?: LookupResolutions;
120
+ /**
121
+ * A connector event's BYOA and webhook-URL facts, or `undefined` when unknown.
122
+ * Supplied by the CLIs from `library.eventFacts` — the call `serialize` makes
123
+ * for the same refusal — so check and compile cannot disagree; omitted (no
124
+ * library) → EVENT_BYOA_UNVERIFIED is not checked.
125
+ */
126
+ eventFacts?: (connector: string, event: string, object?: string) => ByoaEventFacts | undefined;
127
+ /**
128
+ * The BYOA checks `registry prepare` recorded — the `byoa` section of the
129
+ * overlay's `resolutions.json`, as `library.byoaVerifications` loads it.
130
+ */
131
+ byoa?: ByoaLedger;
119
132
  /**
120
133
  * Whether a connector step's schema is materialized locally — the fact behind
121
134
  * the OBJECT_UNPREPARED and CUSTOM_FIELDS_UNPREPARED warnings. Supplied by the
package/dist/check.js CHANGED
@@ -18,6 +18,7 @@ import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step
18
18
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
19
19
  import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
20
20
  import { prepareCommand, resolveFlagArg } from './core/cli-spelling.js';
21
+ import { EVENT_BYOA_UNVERIFIED, byoaUnverified } from './core/byoa.js';
21
22
  export function check(built, opts = {}) {
22
23
  return checkFlow(built, [], opts);
23
24
  }
@@ -1953,6 +1954,13 @@ function eventParamsFor(subscription, opts) {
1953
1954
  * the fallback case for a connector the shipped library was not generated
1954
1955
  * against. A warning, because compile still succeeds. Prints the one
1955
1956
  * command that makes the event checkable.
1957
+ * - `EVENT_BYOA_UNVERIFIED` — an ERROR: the event only fires on a BYOA
1958
+ * (bring-your-own-app) connection, and the bound connection id has no record
1959
+ * of `prepare` having checked it (`core/byoa.ts` has the rule and when it
1960
+ * stays silent). A standard connection deploys and the trigger never fires;
1961
+ * the usual way in is binding the connection by preparing one of the
1962
+ * connector's ACTIONS, which checks nothing about BYOA. `compile` throws the
1963
+ * same text.
1956
1964
  * - `EVENT_FILTER_UNKNOWN_FIELD` — a filter leaf naming a field outside the
1957
1965
  * prepared vocabulary is an ERROR, not a warning, because the platform's own
1958
1966
  * configure DROPS an unmatched leaf and reports Success: the deployed
@@ -1988,6 +1996,9 @@ function checkEventResolution(subscription, where, kind, opts, error, warn, even
1988
1996
  return;
1989
1997
  }
1990
1998
  const object = typeof sub.object === 'string' && sub.object !== '' ? sub.object : undefined;
1999
+ const byoa = opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
2000
+ if (byoa)
2001
+ error(EVENT_BYOA_UNVERIFIED, byoa);
1991
2002
  if (eventParams?.objectUnprepared && object) {
1992
2003
  // The generic template answers for every object, so resolution SUCCEEDS
1993
2004
  // and nothing downstream notices that the object named here has no
@@ -18,6 +18,7 @@ export function registerBpmnCommands(program) {
18
18
  .description('Check authored TypeScript. Check a .bpmn with `uip maestro bpmn validate`.')
19
19
  .argument('<input>', 'a .bpmn.ts path or bare name')
20
20
  .option('--source', 'check authored TypeScript')
21
+ .option('--graph', 'also print the resolved wiring: every element with the flows leaving it, implied edges marked ~>')
21
22
  .addOption(retiredCompiledOption())
22
23
  .action(async (input, options) => {
23
24
  let mode;
@@ -32,7 +33,10 @@ export function registerBpmnCommands(program) {
32
33
  refuse('flow-sdk bpmn check --compiled: use `uip maestro bpmn validate <file.bpmn>`');
33
34
  return;
34
35
  }
35
- await invoke('bpmn check', () => loadTool(import.meta.url, '../../bpmn/check-cli.js'), [input]);
36
+ const argv = [input];
37
+ if (options.graph)
38
+ argv.push('--graph');
39
+ await invoke('bpmn check', () => loadTool(import.meta.url, '../../bpmn/check-cli.js'), argv);
36
40
  });
37
41
  family.command('tidy')
38
42
  .description('Add diagram layout to a compiled .bpmn artifact.')
@@ -49,10 +53,12 @@ export function registerBpmnCommands(program) {
49
53
  .argument('<input>', 'input .bpmn path')
50
54
  .option('-o, --output <file>', 'output .bpmn.ts path')
51
55
  .option('--import <specifier>', 'SDK import specifier in generated source')
56
+ .option('--style <style>', 'explicit (default) writes one call per element and flow; nested lifts boundary events, gateways and event sub-processes into the nesting constructs where the graph is provably the same')
52
57
  .action(async (input, options) => {
53
58
  const argv = [input];
54
59
  addValue(argv, '-o', options.output);
55
60
  addValue(argv, '--import', options.import);
61
+ addValue(argv, '--style', options.style);
56
62
  await invoke('bpmn decompile', () => loadTool(import.meta.url, '../../bpmn/decompile-cli.js'), argv);
57
63
  });
58
64
  family
@@ -19,3 +19,10 @@ export declare function openCliLibrary(argv: readonly string[]): Library | undef
19
19
  * exactly when compile would throw, with the same text.
20
20
  */
21
21
  export declare function eventResolveErrorFor(library: Library): NonNullable<CheckOpts['eventResolveError']>;
22
+ /**
23
+ * `check`'s EVENT_BYOA_UNVERIFIED inputs: the event facts and the recorded BYOA
24
+ * checks, from the same `library.eventFacts` / `library.byoaVerifications` that
25
+ * `serialize` reads for the same refusal. Both CLIs spread this into their
26
+ * check options, so the inner loop and the emit rung refuse the same flows.
27
+ */
28
+ export declare function byoaCheckOpts(library: Library): Pick<CheckOpts, 'eventFacts' | 'byoa'>;
@@ -54,3 +54,22 @@ export function eventResolveErrorFor(library) {
54
54
  }
55
55
  };
56
56
  }
57
+ /**
58
+ * `check`'s EVENT_BYOA_UNVERIFIED inputs: the event facts and the recorded BYOA
59
+ * checks, from the same `library.eventFacts` / `library.byoaVerifications` that
60
+ * `serialize` reads for the same refusal. Both CLIs spread this into their
61
+ * check options, so the inner loop and the emit rung refuse the same flows.
62
+ */
63
+ export function byoaCheckOpts(library) {
64
+ return {
65
+ eventFacts: (connector, event, object) => {
66
+ try {
67
+ return library.eventFacts(connector, event, object);
68
+ }
69
+ catch {
70
+ return undefined; // unknown fails open, same as every library callback
71
+ }
72
+ },
73
+ byoa: library.byoaVerifications,
74
+ };
75
+ }
@@ -23,12 +23,13 @@ import { basename, dirname, join, resolve } from 'node:path';
23
23
  import { resolveFlowFile, loadBuiltFlow } from './load.js';
24
24
  import { serializeWithSidecars, connectorInputErrors } from './serialize.js';
25
25
  import { check } from './check.js';
26
- import { eventResolveErrorFor, openCliLibrary } from './cli-library.js';
26
+ import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
27
27
  import { Bindings } from './bindings.js';
28
28
  import { runWhenInvokedDirectly } from './cli-run.js';
29
29
  import { connectionResources, mergeBindingsV2, readBindingsV2 } from './bindings-v2.js';
30
30
  import { ensureTypeScriptRuntime } from './node-runtime.js';
31
31
  import { BindingRefusal } from './core/binding-messages.js';
32
+ import { ByoaRefusal } from './core/byoa.js';
32
33
  function opt(argv, name) {
33
34
  const i = argv.indexOf(name);
34
35
  return i >= 0 ? argv[i + 1] : undefined;
@@ -96,6 +97,7 @@ export async function run(argv) {
96
97
  // printed as `✗ [EVENT_UNKNOWN]` before stopping instead of hiding
97
98
  // behind "1 error(s); not emitting".
98
99
  eventResolveError: eventResolveErrorFor(library),
100
+ ...byoaCheckOpts(library),
99
101
  eventParams: (connector, event) => {
100
102
  try {
101
103
  return library.eventParameterFields(connector, event);
@@ -134,16 +136,17 @@ export async function run(argv) {
134
136
  // check()'s. Warnings are never fatal: the .flow is still written, exit stays 0.
135
137
  // A binding LABEL the serializer refuses (its own name as the resourceKey, or a
136
138
  // label a populated bindings.json does not declare) is an error, not a
137
- // warning: the check pass above reports it first, and this is the same refusal
138
- // in the same `✗ [CODE]` shape when that pass was skipped (`--no-check`, or an
139
- // emit-only project). Nothing is written.
139
+ // warning, and so is a BYOA-only event on a connection `prepare` never checked
140
+ // (EVENT_BYOA_UNVERIFIED): the check pass above reports each first, and this is
141
+ // the same refusal in the same `✗ [CODE]` shape when that pass was skipped
142
+ // (`--no-check`, or an emit-only project). Nothing is written.
140
143
  const warnings = [];
141
144
  let emitted;
142
145
  try {
143
146
  emitted = serializeWithSidecars(built, { library, bindings, warn: (w) => warnings.push(w) });
144
147
  }
145
148
  catch (error) {
146
- if (!(error instanceof BindingRefusal))
149
+ if (!(error instanceof BindingRefusal || error instanceof ByoaRefusal))
147
150
  throw error;
148
151
  for (const w of warnings)
149
152
  console.error(` ⚠ [${w.code}] ${w.message}`);
@@ -0,0 +1,69 @@
1
+ /** The diagnostic code, in `check` and on the error `serialize` throws. */
2
+ export declare const EVENT_BYOA_UNVERIFIED = "EVENT_BYOA_UNVERIFIED";
3
+ /** One verification `prepare` recorded. Only a PASSING check is ever written. */
4
+ export interface ByoaRecord {
5
+ byoa: true;
6
+ /** The connection's `Name`, so a person reading the file can tell which one it is. */
7
+ connectionName?: string;
8
+ /** `YYYY-MM-DD`: a resolved answer is a snapshot, like every other ledger row. */
9
+ verifiedAt?: string;
10
+ }
11
+ /**
12
+ * The ledger's `byoa` section: `byoa[<event node type>][<connection id>]`.
13
+ *
14
+ * Keyed per EVENT, not per connector — Slack `send-reply` needs no BYOA, the
15
+ * message trigger does — and per CONNECTION, so swapping the connection in
16
+ * bindings.json un-verifies the event until it is prepared again.
17
+ */
18
+ export type ByoaLedger = Record<string, Record<string, ByoaRecord>>;
19
+ /** The event facts the rule reads, as `Library.eventFacts` answers them. */
20
+ export interface ByoaEventFacts {
21
+ byoaConnection: boolean;
22
+ isWebhookUrlVisible: boolean;
23
+ }
24
+ /**
25
+ * The key an event's record is stored under: the WAIT form of the node type,
26
+ * the form `prepare` stores an event's overlay and lookup rows under, whichever
27
+ * role (`onEvent` or `waitForEvent`) the flow uses it in.
28
+ */
29
+ export declare function byoaLedgerKey(connectorKey: string, event: string): string;
30
+ /** The record for `connectionId` under `nodeType`, matching the id case-insensitively. */
31
+ export declare function byoaRecordFor(ledger: ByoaLedger | undefined, nodeType: string, connectionId: string): ByoaRecord | undefined;
32
+ /** What `compile` throws for the refusal; `code` lets the CLI print it as `✗ [CODE]`. */
33
+ export declare class ByoaRefusal extends Error {
34
+ readonly code = "EVENT_BYOA_UNVERIFIED";
35
+ constructor(message: string);
36
+ }
37
+ /** One subscription, as the rule needs it. */
38
+ export interface ByoaSubscription {
39
+ connector: string;
40
+ event: string;
41
+ object?: string;
42
+ /** The `connection:` label as authored — a bindings.json name, or a connection id. */
43
+ connection?: unknown;
44
+ }
45
+ /**
46
+ * Why a BYOA-only event cannot be trusted to fire on its bound connection, or
47
+ * `undefined` when there is nothing to refuse. The ONE condition behind
48
+ * `check`'s EVENT_BYOA_UNVERIFIED error and `compile`'s refusal, so the two
49
+ * cannot disagree.
50
+ *
51
+ * Refuses when the event's facts say BYOA and the connection resolves to a real
52
+ * connection id with no `byoa` record for this event. Silent when:
53
+ *
54
+ * - the facts are unknown or say no BYOA;
55
+ * - the connection does not resolve to a real id — no bindings.json, a label it
56
+ * does not declare, or a placeholder (`0000…`, `<…>`, the label itself). There
57
+ * is no id to look a record up by, and nothing is lost: such a flow cannot
58
+ * run its trigger on any connection, and BINDING_SELF_NAME /
59
+ * BINDING_UNDECLARED / CONNECTION_STUB already say so.
60
+ *
61
+ * @param where - The call site, as the other event diagnostics name it (`trigger`, `waitForEvent "x"`).
62
+ * @param sub - The subscription.
63
+ * @param facts - The event's facts from the library, or `undefined` when unknown.
64
+ * @param ledger - The overlay ledger's `byoa` section.
65
+ * @param bindings - Resolves the `connection:` label to an id.
66
+ */
67
+ export declare function byoaUnverified(where: string, sub: ByoaSubscription, facts: ByoaEventFacts | undefined, ledger: ByoaLedger | undefined, bindings: {
68
+ resolve(name?: string): string | undefined;
69
+ } | undefined): string | undefined;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * core/byoa — connector events that only fire on a BYOA connection (#848).
3
+ *
4
+ * BYOA is "bring your own app": an Integration Service connection created with
5
+ * the customer's own OAuth app. Some event objects need one — Slack
6
+ * `message-received-in-slack` is a webhook the customer's Slack app delivers,
7
+ * so on a standard connection the trigger deploys, validates, and never fires.
8
+ *
9
+ * Only a live call can tell a BYOA connection from a standard one, and `registry
10
+ * prepare` makes it: on an event whose facts say BYOA it lists the tenant's BYOA
11
+ * connections, refuses a connection that is not among them, and records one
12
+ * that is under `byoa[<event node type>][<connection id>]` in
13
+ * `connectors-local/resolutions.json`. This module is the offline half: the
14
+ * record's shape, and the one condition `check` reports and `compile` throws
15
+ * when a BYOA event's bound connection carries no record — typically because
16
+ * the connection was bound by preparing an ACTION of the same connector, which
17
+ * checks nothing about BYOA. `check` stays offline: it checks that the live
18
+ * check happened, never the tenant.
19
+ *
20
+ * No `node:fs` here — `check` and `serialize` import this module, and the file
21
+ * read lives in `lookup-store.ts` beside the ledger's other reader.
22
+ */
23
+ import { GUID_RE } from './binding-messages.js';
24
+ import { prepareCommand } from './cli-spelling.js';
25
+ /** The diagnostic code, in `check` and on the error `serialize` throws. */
26
+ export const EVENT_BYOA_UNVERIFIED = 'EVENT_BYOA_UNVERIFIED';
27
+ /**
28
+ * The key an event's record is stored under: the WAIT form of the node type,
29
+ * the form `prepare` stores an event's overlay and lookup rows under, whichever
30
+ * role (`onEvent` or `waitForEvent`) the flow uses it in.
31
+ */
32
+ export function byoaLedgerKey(connectorKey, event) {
33
+ return `uipath.connector.event.${connectorKey}.${event}`;
34
+ }
35
+ /** The record for `connectionId` under `nodeType`, matching the id case-insensitively. */
36
+ export function byoaRecordFor(ledger, nodeType, connectionId) {
37
+ const perEvent = ledger?.[nodeType];
38
+ if (!perEvent || typeof perEvent !== 'object')
39
+ return undefined;
40
+ const wanted = connectionId.toLowerCase();
41
+ for (const [id, record] of Object.entries(perEvent)) {
42
+ if (id.toLowerCase() === wanted && record && typeof record === 'object' && record.byoa === true)
43
+ return record;
44
+ }
45
+ return undefined;
46
+ }
47
+ /** What `compile` throws for the refusal; `code` lets the CLI print it as `✗ [CODE]`. */
48
+ export class ByoaRefusal extends Error {
49
+ code = EVENT_BYOA_UNVERIFIED;
50
+ constructor(message) {
51
+ super(message);
52
+ this.name = 'ByoaRefusal';
53
+ }
54
+ }
55
+ /** An all-zero GUID: a placeholder no tenant issues (compile's CONNECTION_STUB). */
56
+ const STUB_GUID_RE = /^0{8}-0{4}-0{4}-0{4}-/;
57
+ /**
58
+ * Why a BYOA-only event cannot be trusted to fire on its bound connection, or
59
+ * `undefined` when there is nothing to refuse. The ONE condition behind
60
+ * `check`'s EVENT_BYOA_UNVERIFIED error and `compile`'s refusal, so the two
61
+ * cannot disagree.
62
+ *
63
+ * Refuses when the event's facts say BYOA and the connection resolves to a real
64
+ * connection id with no `byoa` record for this event. Silent when:
65
+ *
66
+ * - the facts are unknown or say no BYOA;
67
+ * - the connection does not resolve to a real id — no bindings.json, a label it
68
+ * does not declare, or a placeholder (`0000…`, `<…>`, the label itself). There
69
+ * is no id to look a record up by, and nothing is lost: such a flow cannot
70
+ * run its trigger on any connection, and BINDING_SELF_NAME /
71
+ * BINDING_UNDECLARED / CONNECTION_STUB already say so.
72
+ *
73
+ * @param where - The call site, as the other event diagnostics name it (`trigger`, `waitForEvent "x"`).
74
+ * @param sub - The subscription.
75
+ * @param facts - The event's facts from the library, or `undefined` when unknown.
76
+ * @param ledger - The overlay ledger's `byoa` section.
77
+ * @param bindings - Resolves the `connection:` label to an id.
78
+ */
79
+ export function byoaUnverified(where, sub, facts, ledger, bindings) {
80
+ if (!facts?.byoaConnection)
81
+ return undefined;
82
+ if (typeof sub.connection !== 'string' || sub.connection === '')
83
+ return undefined;
84
+ const label = sub.connection;
85
+ const id = bindings?.resolve(label) ?? label;
86
+ if (!GUID_RE.test(id) || STUB_GUID_RE.test(id))
87
+ return undefined;
88
+ if (byoaRecordFor(ledger, byoaLedgerKey(sub.connector, sub.event), id))
89
+ return undefined;
90
+ const named = label === id ? `connection ${id}` : `connection \`${label}\` (${id})`;
91
+ const object = sub.object ? ` --object ${sub.object}` : '';
92
+ return `${where}: \`${sub.connector}\` \`${sub.event}\` requires a BYOA (bring-your-own-app) connection — `
93
+ + `one created with your own OAuth app — and ${named} was never checked against that requirement. `
94
+ + `A standard connection deploys and the trigger never fires. Prepare the event on this connection, `
95
+ + `which refuses a standard connection and records a BYOA one:\n`
96
+ + ` ${prepareCommand(sub.connector, sub.event)}${object} --connection-id ${id}\n`
97
+ + `BYOA connections (rows for \`${sub.connector}\`): uip is connections list --byoa --all-folders`;
98
+ }
@@ -1,4 +1,5 @@
1
1
  import { type LookupResolutions, type LookupSpec } from './lookups.js';
2
+ import type { ByoaEventFacts, ByoaLedger } from './byoa.js';
2
3
  export interface ResolvedConnector {
3
4
  nodeType: string;
4
5
  version: string;
@@ -229,6 +230,12 @@ export declare class Library {
229
230
  * id from someone else's connection.
230
231
  */
231
232
  readonly lookupResolutions: LookupResolutions;
233
+ /**
234
+ * The BYOA checks `prepare` recorded (`resolutions.json` → `byoa`), from the
235
+ * overlay only, for the same reason as {@link lookupResolutions}: whether a
236
+ * connection is BYOA is a fact about one tenant.
237
+ */
238
+ readonly byoaVerifications: ByoaLedger;
232
239
  /** Collections the ledger declares connector-invariant; overlay unions over baked. */
233
240
  private readonly invariantCollections;
234
241
  constructor(dir: string, opts?: LibraryOpts);
@@ -348,6 +355,22 @@ export declare class Library {
348
355
  * raw library snapshot carries ZERO trigger/event entries).
349
356
  */
350
357
  eventParameterFields(key: string, event: string, objectName?: string): EventParameterFields | undefined;
358
+ /**
359
+ * Whether a connector event needs a BYOA (bring-your-own-app) connection, and
360
+ * whether its webhook URL must be registered by hand — or `undefined` when no
361
+ * layer carries the event in either form.
362
+ *
363
+ * Read from the `.v1def.json` sidecar, where the library build stamps the
364
+ * facts from the event's `triggers objects` row and `prepare` stores them on
365
+ * the overlay. A fact is TRUE when ANY layer, in either form (`.event.` or
366
+ * `.trigger.`), says so: the requirement belongs to the event object, not to
367
+ * a tenant, and an overlay written by an older `prepare` — which stored the
368
+ * `describe` answer, `false` for Slack `message-received-in-slack` — must not
369
+ * hide the true fact a newer baked library carries.
370
+ *
371
+ * `check` and `compile` read this through the one rule in `core/byoa.ts`.
372
+ */
373
+ eventFacts(key: string, event: string, objectName?: string): ByoaEventFacts | undefined;
351
374
  /**
352
375
  * The filterable-field names a connector event declares — the vocabulary a
353
376
  * `filters` leaf may name.