@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
@@ -11,7 +11,7 @@ import { join } from 'node:path';
11
11
  import { prepareCommand } from './cli-spelling.js';
12
12
  import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
13
13
  import { lookupSpecOf } from './lookups.js';
14
- import { loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
14
+ import { loadByoaLedger, loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
15
15
  export class Library {
16
16
  layers = [];
17
17
  /** The LIST-ENVELOPE LEDGER, overlay first so a prepared entry can add a row. */
@@ -24,10 +24,17 @@ export class Library {
24
24
  * id from someone else's connection.
25
25
  */
26
26
  lookupResolutions;
27
+ /**
28
+ * The BYOA checks `prepare` recorded (`resolutions.json` → `byoa`), from the
29
+ * overlay only, for the same reason as {@link lookupResolutions}: whether a
30
+ * connection is BYOA is a fact about one tenant.
31
+ */
32
+ byoaVerifications;
27
33
  /** Collections the ledger declares connector-invariant; overlay unions over baked. */
28
34
  invariantCollections;
29
35
  constructor(dir, opts = {}) {
30
36
  this.lookupResolutions = loadLookupResolutions(opts.overlayDir);
37
+ this.byoaVerifications = loadByoaLedger(opts.overlayDir);
31
38
  this.invariantCollections = new Set([
32
39
  ...loadInvariantCollections(dir),
33
40
  ...loadInvariantCollections(opts.overlayDir),
@@ -434,6 +441,45 @@ export class Library {
434
441
  .some(([name, value]) => name !== 'type' && Boolean(value)),
435
442
  };
436
443
  }
444
+ /**
445
+ * Whether a connector event needs a BYOA (bring-your-own-app) connection, and
446
+ * whether its webhook URL must be registered by hand — or `undefined` when no
447
+ * layer carries the event in either form.
448
+ *
449
+ * Read from the `.v1def.json` sidecar, where the library build stamps the
450
+ * facts from the event's `triggers objects` row and `prepare` stores them on
451
+ * the overlay. A fact is TRUE when ANY layer, in either form (`.event.` or
452
+ * `.trigger.`), says so: the requirement belongs to the event object, not to
453
+ * a tenant, and an overlay written by an older `prepare` — which stored the
454
+ * `describe` answer, `false` for Slack `message-received-in-slack` — must not
455
+ * hide the true fact a newer baked library carries.
456
+ *
457
+ * `check` and `compile` read this through the one rule in `core/byoa.ts`.
458
+ */
459
+ eventFacts(key, event, objectName) {
460
+ let found = false;
461
+ const facts = { byoaConnection: false, isWebhookUrlVisible: false };
462
+ for (const layer of this.layers) {
463
+ for (const kind of ['event', 'trigger']) {
464
+ let rc;
465
+ try {
466
+ rc = this.resolveInLayer(layer, `uipath.connector.${kind}.${key}.${event}`, undefined, objectName);
467
+ }
468
+ catch {
469
+ continue; // an ambiguous object is resolveEvent's refusal to report
470
+ }
471
+ if (!rc)
472
+ continue;
473
+ found = true;
474
+ const v1def = (rc.v1def ?? {});
475
+ if (v1def.byoaConnection === true)
476
+ facts.byoaConnection = true;
477
+ if (v1def.isWebhookUrlVisible === true)
478
+ facts.isWebhookUrlVisible = true;
479
+ }
480
+ }
481
+ return found ? facts : undefined;
482
+ }
437
483
  /**
438
484
  * The filterable-field names a connector event declares — the vocabulary a
439
485
  * `filters` leaf may name.
@@ -1,3 +1,4 @@
1
+ import type { ByoaLedger } from './byoa.js';
1
2
  import type { LookupResolutions } from './lookups.js';
2
3
  /**
3
4
  * Load `<overlayDir>/resolutions.json`, or an empty map.
@@ -8,6 +9,17 @@ import type { LookupResolutions } from './lookups.js';
8
9
  * behaviour an author wants from a missing resolution anyway.
9
10
  */
10
11
  export declare function loadLookupResolutions(overlayDir: string | undefined): LookupResolutions;
12
+ /**
13
+ * Load the `byoa` section of `<overlayDir>/resolutions.json` — the BYOA checks
14
+ * `registry prepare` recorded (see `core/byoa.ts`) — or an empty map.
15
+ *
16
+ * Lenient like {@link loadLookupResolutions}: an absent or malformed file reads
17
+ * as EMPTY, and the consequence of empty is the EVENT_BYOA_UNVERIFIED error
18
+ * naming the `prepare` command that records the check. Read fresh on every
19
+ * call rather than cached: `prepare` rewrites this file, and a stale "never
20
+ * checked" answer would refuse a flow that was just verified.
21
+ */
22
+ export declare function loadByoaLedger(overlayDir: string | undefined): ByoaLedger;
11
23
  /**
12
24
  * The invariant-collection ledger's `objectName` set, from a library dir.
13
25
  *
@@ -45,6 +45,31 @@ export function loadLookupResolutions(overlayDir) {
45
45
  RESOLUTION_CACHE.set(overlayDir, rows);
46
46
  return rows;
47
47
  }
48
+ /**
49
+ * Load the `byoa` section of `<overlayDir>/resolutions.json` — the BYOA checks
50
+ * `registry prepare` recorded (see `core/byoa.ts`) — or an empty map.
51
+ *
52
+ * Lenient like {@link loadLookupResolutions}: an absent or malformed file reads
53
+ * as EMPTY, and the consequence of empty is the EVENT_BYOA_UNVERIFIED error
54
+ * naming the `prepare` command that records the check. Read fresh on every
55
+ * call rather than cached: `prepare` rewrites this file, and a stale "never
56
+ * checked" answer would refuse a flow that was just verified.
57
+ */
58
+ export function loadByoaLedger(overlayDir) {
59
+ if (!overlayDir)
60
+ return {};
61
+ const file = join(overlayDir, 'resolutions.json');
62
+ if (!existsSync(file))
63
+ return {};
64
+ try {
65
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
66
+ const byoa = parsed?.byoa;
67
+ return byoa && typeof byoa === 'object' && !Array.isArray(byoa) ? byoa : {};
68
+ }
69
+ catch {
70
+ return {};
71
+ }
72
+ }
48
73
  /**
49
74
  * The invariant-collection ledger's `objectName` set, from a library dir.
50
75
  *
package/dist/index.d.ts CHANGED
@@ -24,9 +24,12 @@ export type { CaseRule, CaseRuleInput, CaseRuleGrid, WhenExpression, CaseJsonPre
24
24
  export { serialize as serializeCase } from './case/serialize.js';
25
25
  export { decompile as decompileCase, decompileWithDiagnostics as decompileCaseWithDiagnostics } from './case/decompile.js';
26
26
  export { bpmn } from './bpmn/bpmn-sdk.js';
27
- export type { BuiltBpmn, BpmnNode, BpmnFlow, BpmnVarDecl, EventDef, GatewayKind, EventKind, LoopSpec, RetrySpec, ActivityOpts, HttpOpts, } from './bpmn/bpmn-sdk.js';
27
+ export type { BuiltBpmn, BpmnNode, BpmnFlow, BpmnVarDecl, EventDef, GatewayKind, EventKind, LoopSpec, RetrySpec, ActivityOpts, HttpOpts, ActivityBodyFn, ArmBodyFn, HandlerOpts, ErrorHandlerOpts, TimerHandlerOpts, MessageHandlerOpts, ChooseArm, ChooseOpts, SplitOpts, JoinOpts, EventSubProcessOpts, FlowMode, } from './bpmn/bpmn-sdk.js';
28
+ export { PathBuilder, ActivityBuilder } from './bpmn/bpmn-sdk.js';
29
+ export { renderGraph as renderBpmnGraph, graphModel as bpmnGraphModel } from './bpmn/graph.js';
30
+ export type { GraphScope as BpmnGraphScope, GraphNode as BpmnGraphNode, GraphEdge as BpmnGraphEdge } from './bpmn/graph.js';
28
31
  export { registryTypeNames, registryType, isSnapshotType } from './bpmn/typed-node.js';
29
- export type { RegistryTypeSpec, RegistryContextField, RegistryOutput } from './bpmn/typed-node.js';
32
+ export type { RegistryTypeSpec, RegistryContextField, RegistryInputField, RegistryOutput, TypedContextRow, TypedInputRow, TypedOutputRow } from './bpmn/typed-node.js';
30
33
  export { serialize as serializeBpmn } from './bpmn/serialize.js';
31
34
  export { check as checkBpmn } from './bpmn/check.js';
32
35
  export type { Diagnostic as BpmnDiagnostic } from './bpmn/check.js';
package/dist/index.js CHANGED
@@ -27,6 +27,9 @@ export { serialize as serializeCase } from './case/serialize.js';
27
27
  export { decompile as decompileCase, decompileWithDiagnostics as decompileCaseWithDiagnostics } from './case/decompile.js';
28
28
  // BPMN authoring API. `bpmn()` exposes only BPMN methods.
29
29
  export { bpmn } from './bpmn/bpmn-sdk.js';
30
+ export { PathBuilder, ActivityBuilder } from './bpmn/bpmn-sdk.js';
31
+ // The resolved wiring `bpmn check --graph` prints, for a tool that wants it as data.
32
+ export { renderGraph as renderBpmnGraph, graphModel as bpmnGraphModel } from './bpmn/graph.js';
30
33
  // The registry snapshot the typed nodes emit from — exposed so the coverage
31
34
  // ledger and its audit read the same source the emitter does. `isSnapshotType`
32
35
  // separates the two answers `registryType` now gives: a type the snapshot carries
@@ -40,7 +40,7 @@ export declare function emitLookupsBlock(specs: ReadonlyMap<string, LookupSpecDa
40
40
  * parameters (HTTP Webhook) or one nothing has described yet, and an empty
41
41
  * interface would refuse every key in both cases.
42
42
  */
43
- export declare function emitEvent(entry: LibraryEntry, inFields: readonly LibraryField[], inputTree: TypeTree, outputTree: TypeTree, name: string, inputTypes: NamedTypeRegistry, outputTypes: NamedTypeRegistry, invariant?: ReadonlySet<string>): string[];
43
+ export declare function emitEvent(entry: LibraryEntry, inFields: readonly LibraryField[], inputTree: TypeTree, outputTree: TypeTree, name: string, inputTypes: NamedTypeRegistry, outputTypes: NamedTypeRegistry, invariant?: ReadonlySet<string>, v1def?: V1Definition): string[];
44
44
  /** One operation: its `Inputs`/`Outputs` interfaces, doc comment and `descriptor()` const. */
45
45
  export declare function emitOp(entry: LibraryEntry, v1def: V1Definition, inputTree: TypeTree, outputTree: TypeTree, name: string, inputTypes: NamedTypeRegistry, outputTypes: NamedTypeRegistry, invariant?: ReadonlySet<string>): string[];
46
46
  /**
@@ -18,6 +18,7 @@
18
18
  * event's per-object variants are skipped because they share their template's
19
19
  * node type and so would emit the same const twice.
20
20
  */
21
+ import { prepareCommand } from "../../core/cli-spelling.js";
21
22
  import { PACKAGE_NAME } from "../../package-name.js";
22
23
  import { pyJsonString, pyJsonStringify } from "../codecs/pyjson.js";
23
24
  import { sortedByCodePoint, sortedStrings } from "../compat/compare.js";
@@ -165,7 +166,7 @@ function versionOf(entry) {
165
166
  * parameters (HTTP Webhook) or one nothing has described yet, and an empty
166
167
  * interface would refuse every key in both cases.
167
168
  */
168
- export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTypes, outputTypes, invariant = new Set()) {
169
+ export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTypes, outputTypes, invariant = new Set(), v1def = {}) {
169
170
  const nodeType = entry.nodeType;
170
171
  const eventId = afterLast(nodeType, ".");
171
172
  const connectorKey = pyTruthy(entry.connector?.key) ? entry.connector.key : "";
@@ -196,6 +197,23 @@ export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTyp
196
197
  lines.push(` * Start a flow on it with \`onEvent(${name}, { where: { … } })\`, or pause for it`);
197
198
  lines.push(` * with \`waitForEvent(${name}, { … })\`; \`where\` carries its event parameters.`);
198
199
  }
200
+ // The facts the library build stamped from the event's `triggers objects`
201
+ // row. An author holding only the descriptor must learn the requirement here:
202
+ // on a standard connection the trigger deploys and never fires.
203
+ if (v1def.byoaConnection === true) {
204
+ lines.push(" *");
205
+ lines.push(" * Requires a BYOA (bring-your-own-app) connection — on a standard one the");
206
+ lines.push(" * trigger deploys and never fires. Bind it with");
207
+ // Spelled for whoever ran the generator, like every `check` remedy.
208
+ lines.push(` * \`${prepareCommand(pyStr(connectorKey), eventId, "--connection-id", "<id>")}\`,`);
209
+ lines.push(" * which refuses a standard connection and records the check `compile` requires;");
210
+ lines.push(" * `uip is connections list --byoa --all-folders` lists the BYOA connections.");
211
+ }
212
+ if (v1def.isWebhookUrlVisible === true) {
213
+ lines.push(" *");
214
+ lines.push(" * After deploying, register the trigger's webhook URL in the connector app's");
215
+ lines.push(" * event-subscription settings; `registry prepare` prints the instructions.");
216
+ }
199
217
  // INSIDE the block: `emitLookupComment` returns bare ` * ` body lines.
200
218
  if (specs.size > 0) {
201
219
  lines.push(" *");
@@ -222,7 +240,7 @@ export function emitOp(entry, v1def, inputTree, outputTree, name, inputTypes, ou
222
240
  const runtime = entry.runtime ?? {};
223
241
  const inFields = descriptorInputFields(entry, v1def);
224
242
  if (isEventNodeType(nodeType)) {
225
- return emitEvent(entry, inFields, inputTree, outputTree, name, inputTypes, outputTypes, invariant);
243
+ return emitEvent(entry, inFields, inputTree, outputTree, name, inputTypes, outputTypes, invariant, v1def);
226
244
  }
227
245
  const lines = [];
228
246
  lines.push(...emitInterface(`${name}Inputs`, false, inputTypes, inputTree, true));
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * One library entry rendered to Markdown: header → schema-dynamic notice →
3
- * choosing the object → resolving the field set → required inputs → optional
3
+ * connection requirements (events) → choosing the object → resolving the field set → required inputs → optional
4
4
  * inputs → resolving lookups → example → filtering → outputs. Ported from
5
5
  * `render_op`; the page is `"\n".join(lines)` with no trailing newline.
6
6
  */
7
7
  import { afterLast, pyStr } from "../../compat/pystr.js";
8
8
  import { dictOf, getStr, pyOr, pyTruthy } from "./pyvalue.js";
9
9
  import { choosingObjectLines } from "./sections/choosing-object.js";
10
+ import { eventFactsLines } from "./sections/event-facts.js";
10
11
  import { exampleLines } from "./sections/example.js";
11
12
  import { eventFilterLines } from "./sections/filtering.js";
12
13
  import { headerLines } from "./sections/header.js";
@@ -28,6 +29,7 @@ export function renderOp(j, v1def, symbol, invariant) {
28
29
  const out = [];
29
30
  out.push(...headerLines(j, nodeType));
30
31
  out.push(...schemaDynamicLines(j, nodeType));
32
+ out.push(...eventFactsLines(j, v1def));
31
33
  const fields = assembleFields(j, v1def);
32
34
  const required = fields.filter((f) => pyTruthy(f.required));
33
35
  const optional = fields.filter((f) => !pyTruthy(f.required));
@@ -0,0 +1,2 @@
1
+ import { type Dict } from "../pyvalue.js";
2
+ export declare function eventFactsLines(j: Dict, v1def: Dict | undefined): string[];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `## Connection requirements`: the event facts an author has to act on before
3
+ * the trigger can fire — a BYOA (bring-your-own-app) connection, and a webhook
4
+ * URL registered by hand. Emitted only when a fact is TRUE, so every other
5
+ * event page is unchanged. The facts are the sidecar's, which the library build
6
+ * stamps from the event's `triggers objects` row (#848).
7
+ */
8
+ import { prepareCommand } from "../../../../core/cli-spelling.js";
9
+ import { isEventNodeType } from "../../../model/library.js";
10
+ import { dictOf, getStr } from "../pyvalue.js";
11
+ export function eventFactsLines(j, v1def) {
12
+ const nodeType = getStr(j, "nodeType", "");
13
+ if (!isEventNodeType(nodeType))
14
+ return [];
15
+ const facts = dictOf(v1def);
16
+ const byoa = facts.byoaConnection === true;
17
+ const webhook = facts.isWebhookUrlVisible === true;
18
+ if (!byoa && !webhook)
19
+ return [];
20
+ const key = getStr(dictOf(j.connector), "key", "<connector>");
21
+ const event = nodeType.slice(nodeType.lastIndexOf(".") + 1);
22
+ const out = ["## Connection requirements", ""];
23
+ if (byoa) {
24
+ out.push("**Requires a BYOA connection.** This event fires only on a BYOA " +
25
+ "(bring-your-own-app) connection — one created with your own OAuth app. " +
26
+ "On a standard connection the trigger deploys and never fires. Prepare " +
27
+ "the event on the connection you bind: it refuses a standard connection " +
28
+ "and records a BYOA one, and `check` and `compile` refuse this event on a " +
29
+ "connection that was never checked (`EVENT_BYOA_UNVERIFIED`).");
30
+ out.push("");
31
+ out.push("```bash");
32
+ out.push(prepareCommand(key, event, "--connection-id", "<id>"));
33
+ out.push("uip is connections list --byoa --all-folders # the BYOA connections");
34
+ out.push("```");
35
+ out.push("");
36
+ }
37
+ if (webhook) {
38
+ out.push("**Register the webhook URL.** The connector's app delivers these events " +
39
+ "to a URL Integration Service issues. After deploying, copy the trigger's " +
40
+ "webhook URL into the app's event-subscription settings; `registry prepare` " +
41
+ "prints the connector's own instructions.");
42
+ out.push("");
43
+ }
44
+ return out;
45
+ }
@@ -48,15 +48,43 @@ export declare function normalizeBucketFields(parameters: unknown): Dict[];
48
48
  * survives the merge.
49
49
  */
50
50
  export declare function unionEventParameters(declared: unknown, objectParameters: unknown): Dict[];
51
- /** `merge_describe_into_node`: fold a `triggers describe` answer into a registry trigger definition (a copy). */
52
- export declare function mergeDescribeIntoNode(node: Dict, describe: Dict, objectParameters?: unknown): Dict;
51
+ /** The three event facts: the API's (PascalCase) spelling and the stored (camelCase) one. */
52
+ declare const EVENT_FACTS: ReadonlyArray<readonly [string, "eventMode" | "byoaConnection" | "isWebhookUrlVisible"]>;
53
+ /** The facts one source states, under their stored names; a fact the source does not state is absent. */
54
+ export type StatedEventFacts = Partial<Record<(typeof EVENT_FACTS)[number][1], unknown>>;
55
+ /**
56
+ * THE precedence rule for the three event facts, used by every place that
57
+ * decides one: the `triggers objects` ROW wins, then `fallback`, and a fact
58
+ * neither states stays absent.
59
+ *
60
+ * `fallback` is whichever definition the caller holds — a `triggers describe`
61
+ * answer (PascalCase, generate time) or a registry node (camelCase, prepare
62
+ * time); both spellings are read. The row is the only one of the three sources
63
+ * that tells the truth about BYOA: Slack `message-received-in-slack` answers
64
+ * `ByoaConnection: true` in its objects row and `false` in both `describe` and
65
+ * the registry node. Generate time used to read `describe` alone while
66
+ * `prepare` read the row, so the library baked `false` for a BYOA-only event
67
+ * and nothing downstream could refuse a standard connection on it (#848).
68
+ * One function, so the two sides cannot drift again.
69
+ */
70
+ export declare function resolveEventFacts(objectRow: unknown, fallback: unknown): StatedEventFacts;
71
+ /**
72
+ * `merge_describe_into_node`: fold a `triggers describe` answer into a registry
73
+ * trigger definition (a copy). The facts follow {@link resolveEventFacts}, so
74
+ * pass the event's `triggers objects` row when there is one: `prepare` stores
75
+ * this definition as the overlay, and `check` reads BYOA off the overlay.
76
+ */
77
+ export declare function mergeDescribeIntoNode(node: Dict, describe: Dict, objectParameters?: unknown, objectRow?: unknown): Dict;
53
78
  export interface EventFacts {
54
79
  eventMode: string;
55
80
  byoaConnection: boolean;
56
81
  isWebhookUrlVisible: boolean;
57
82
  textBlocks: string[];
58
83
  }
59
- /** `event_facts`: the three facts that change how a subscription is prepared or tested; the object row wins. */
84
+ /**
85
+ * `event_facts`: the three facts that change how a subscription is prepared or
86
+ * tested, by {@link resolveEventFacts} (the object row wins), with defaults.
87
+ */
60
88
  export declare function eventFacts(node: Dict, objectRow: unknown): EventFacts;
61
89
  /**
62
90
  * `triggers_objects_cached`: cached `uip is triggers objects <key> <OPERATION>`,
@@ -106,3 +134,4 @@ export declare function expandGenericEvents(ctx: BuildContext, templates: Librar
106
134
  * input with curated events described in place.
107
135
  */
108
136
  export declare function resolveConnectorEvents(ctx: BuildContext, entries: LibraryEntry[]): Promise<[LibraryEntry[], LibraryEntry[]]>;
137
+ export {};
@@ -261,39 +261,69 @@ export function unionEventParameters(declared, objectParameters) {
261
261
  }
262
262
  return withCascade([...merged.values()]);
263
263
  }
264
+ /** The three event facts: the API's (PascalCase) spelling and the stored (camelCase) one. */
264
265
  const EVENT_FACTS = [
265
266
  ["EventMode", "eventMode"],
266
267
  ["ByoaConnection", "byoaConnection"],
267
268
  ["IsWebhookUrlVisible", "isWebhookUrlVisible"],
268
269
  ];
269
- /** `merge_describe_into_node`: fold a `triggers describe` answer into a registry trigger definition (a copy). */
270
- export function mergeDescribeIntoNode(node, describe, objectParameters = null) {
270
+ /**
271
+ * THE precedence rule for the three event facts, used by every place that
272
+ * decides one: the `triggers objects` ROW wins, then `fallback`, and a fact
273
+ * neither states stays absent.
274
+ *
275
+ * `fallback` is whichever definition the caller holds — a `triggers describe`
276
+ * answer (PascalCase, generate time) or a registry node (camelCase, prepare
277
+ * time); both spellings are read. The row is the only one of the three sources
278
+ * that tells the truth about BYOA: Slack `message-received-in-slack` answers
279
+ * `ByoaConnection: true` in its objects row and `false` in both `describe` and
280
+ * the registry node. Generate time used to read `describe` alone while
281
+ * `prepare` read the row, so the library baked `false` for a BYOA-only event
282
+ * and nothing downstream could refuse a standard connection on it (#848).
283
+ * One function, so the two sides cannot drift again.
284
+ */
285
+ export function resolveEventFacts(objectRow, fallback) {
286
+ const row = pyOr(objectRow, {});
287
+ const other = pyOr(fallback, {});
288
+ const out = {};
289
+ for (const [api, stored] of EVENT_FACTS) {
290
+ for (const [source, key] of [[row, api], [other, api], [other, stored]]) {
291
+ if (get(source, key) !== null) {
292
+ out[stored] = source[key];
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ return out;
298
+ }
299
+ /**
300
+ * `merge_describe_into_node`: fold a `triggers describe` answer into a registry
301
+ * trigger definition (a copy). The facts follow {@link resolveEventFacts}, so
302
+ * pass the event's `triggers objects` row when there is one: `prepare` stores
303
+ * this definition as the overlay, and `check` reads BYOA off the overlay.
304
+ */
305
+ export function mergeDescribeIntoNode(node, describe, objectParameters = null, objectRow = null) {
271
306
  const out = { ...node };
272
307
  out.eventParameters = { fields: unionEventParameters(get(describe, "EventParameters"), objectParameters) };
273
308
  out.filterFields = { fields: camelFields(get(describe, "FilterFields")) };
274
309
  if (truthy(get(describe, "OutputFields"))) {
275
310
  out.outputResponseDefinition = { fields: camelFields(get(describe, "OutputFields")) };
276
311
  }
277
- for (const [src, dst] of EVENT_FACTS)
278
- if (get(describe, src) !== null)
279
- out[dst] = describe[src];
312
+ Object.assign(out, resolveEventFacts(objectRow, describe));
280
313
  return out;
281
314
  }
282
- /** `event_facts`: the three facts that change how a subscription is prepared or tested; the object row wins. */
315
+ /**
316
+ * `event_facts`: the three facts that change how a subscription is prepared or
317
+ * tested, by {@link resolveEventFacts} (the object row wins), with defaults.
318
+ */
283
319
  export function eventFacts(node, objectRow) {
284
320
  const row = pyOr(objectRow, {});
285
- const fact = (rowKey, nodeKey, dflt) => {
286
- if (get(row, rowKey) !== null)
287
- return row[rowKey];
288
- if (get(node, nodeKey) !== null)
289
- return node[nodeKey];
290
- return dflt;
291
- };
321
+ const stated = resolveEventFacts(row, node);
292
322
  const textBlocks = pyOr(get(pyOr(get(row, "Design"), {}), "TextBlocks"), get(pyOr(get(row, "design"), {}), "textBlocks"), []);
293
323
  return {
294
- eventMode: pyStr(pyOr(get(node, "eventMode"), "polling")),
295
- byoaConnection: truthy(fact("ByoaConnection", "byoaConnection", false)),
296
- isWebhookUrlVisible: truthy(fact("IsWebhookUrlVisible", "isWebhookUrlVisible", false)),
324
+ eventMode: pyStr(pyOr(stated.eventMode ?? null, "polling")),
325
+ byoaConnection: truthy(stated.byoaConnection ?? false),
326
+ isWebhookUrlVisible: truthy(stated.isWebhookUrlVisible ?? false),
297
327
  textBlocks: pyIter(textBlocks).map(textBlockText),
298
328
  };
299
329
  }
@@ -388,9 +418,8 @@ export function applyEventSchema(entry, describe, obj) {
388
418
  if (outputFields.length > 0)
389
419
  e.outputSchema = { fields: outputFields };
390
420
  const eventSchema = { eventParameters: fields, filterFields: camelFields(get(d, "FilterFields")) };
391
- for (const [src, dst] of EVENT_FACTS)
392
- if (get(d, src) !== null)
393
- eventSchema[dst] = d[src];
421
+ // The objects row is the per-object fact sheet; describe is the fallback.
422
+ Object.assign(eventSchema, resolveEventFacts(o, d));
394
423
  e.eventSchema = eventSchema;
395
424
  return entry;
396
425
  }
@@ -0,0 +1,12 @@
1
+ import type { ConnectionRow } from "./connections.js";
2
+ /**
3
+ * The ledger with `byoa[nodeType][connectionId]` set to a passing check. Pure:
4
+ * returns a new ledger. A re-run on the same connection rewrites the entry,
5
+ * `verifiedAt` included — the check was made again, so the snapshot is newer.
6
+ */
7
+ export declare function recordByoa(ledger: Record<string, unknown>, nodeType: string, connectionId: string, row: ConnectionRow | undefined, verifiedAt: string): Record<string, unknown>;
8
+ /**
9
+ * Record a passing BYOA check in `<outDir>/resolutions.json` and return that
10
+ * path: read (refusing a corrupt file before anything is written), record, write.
11
+ */
12
+ export declare function recordByoaVerification(outDir: string, nodeType: string, connectionId: string, row: ConnectionRow | undefined, verifiedAt?: string): string;
@@ -0,0 +1,26 @@
1
+ import { ledgerPath, readLedger, writeLedger } from "./objects.js";
2
+ import { isPlainObject, pyGet, pyStrAny } from "./support.js";
3
+ /**
4
+ * The ledger with `byoa[nodeType][connectionId]` set to a passing check. Pure:
5
+ * returns a new ledger. A re-run on the same connection rewrites the entry,
6
+ * `verifiedAt` included — the check was made again, so the snapshot is newer.
7
+ */
8
+ export function recordByoa(ledger, nodeType, connectionId, row, verifiedAt) {
9
+ const sectionIn = ledger.byoa;
10
+ const section = isPlainObject(sectionIn) ? { ...sectionIn } : {};
11
+ const perEventIn = section[nodeType];
12
+ const perEvent = isPlainObject(perEventIn) ? { ...perEventIn } : {};
13
+ const name = pyStrAny(pyGet(row ?? {}, "Name", "") ?? "");
14
+ perEvent[connectionId] = { byoa: true, ...(name ? { connectionName: name } : {}), verifiedAt };
15
+ section[nodeType] = perEvent;
16
+ return { ...ledger, schemaVersion: "1", byoa: section };
17
+ }
18
+ /**
19
+ * Record a passing BYOA check in `<outDir>/resolutions.json` and return that
20
+ * path: read (refusing a corrupt file before anything is written), record, write.
21
+ */
22
+ export function recordByoaVerification(outDir, nodeType, connectionId, row, verifiedAt = new Date().toISOString().slice(0, 10)) {
23
+ const path = ledgerPath(outDir);
24
+ writeLedger(path, recordByoa(readLedger(path), nodeType, connectionId, row, verifiedAt));
25
+ return path;
26
+ }
@@ -3,6 +3,7 @@ import { type EventFacts } from "../library-build/events.js";
3
3
  import { type Dict } from "../library-build/py.js";
4
4
  import type { LibraryEntry } from "../model/library.js";
5
5
  import type { UipClient } from "../uip/client.js";
6
+ import { type ConnectionRow } from "./connections.js";
6
7
  /**
7
8
  * `_event_operation`: the operation key every trigger API takes.
8
9
  *
@@ -50,8 +51,11 @@ export interface BindingsRollback {
50
51
  * connection into `bindings.json` by the time the event's own metadata says it
51
52
  * is the wrong KIND, so the file is restored first. A listing that FAILED is
52
53
  * not a tenant with no BYOA connections; the remedies are opposite.
54
+ *
55
+ * Returns the connection's BYOA listing row when it passes, for the record
56
+ * `prepare` then writes (see `byoa-ledger.ts`).
53
57
  */
54
- export declare function refuseUnlessByoaConnection(client: UipClient, connectorKey: string, connectionId: string, rollback: BindingsRollback | undefined, warn: (line: string) => void): Promise<void>;
58
+ export declare function refuseUnlessByoaConnection(client: UipClient, connectorKey: string, connectionId: string, rollback: BindingsRollback | undefined, warn: (line: string) => void): Promise<ConnectionRow>;
55
59
  export interface EventPrepareOptions {
56
60
  connectorKey: string;
57
61
  action: string;
@@ -21,12 +21,13 @@
21
21
  */
22
22
  import { extractCanonical, nodeOf } from "../library-build/canonical.js";
23
23
  import { registryProbe } from "../library-build/context.js";
24
- import { EventDiscoveryError, eventFacts, mergeDescribeIntoNode, triggersDescribe, triggersObjects, unionEventParameters, } from "../library-build/events.js";
24
+ import { EventDiscoveryError, eventFacts, mergeDescribeIntoNode, resolveEventFacts, triggersDescribe, triggersObjects, unionEventParameters, } from "../library-build/events.js";
25
25
  import { get, isRecord } from "../library-build/py.js";
26
26
  import { planLibrary } from "../library-build/plan.js";
27
+ import { recordByoaVerification } from "./byoa-ledger.js";
27
28
  import { ConnectionRefusal, byoaConnections, byoaListing, restoreBindings } from "./connections.js";
28
29
  import { writeResolutions } from "./ledger.js";
29
- import { PREPARE_COMMAND, PrepareRefusal, byoaListingFailedMessage, byoaRequiredMessage, describeFallbackWarning, generatedModulesLine, eventAllObjectsMessage, eventNextLine, eventParentFieldsMessage, eventSubscription, eventSummaryLine, filterableFieldsLine, genericEventNeedsObjectMessage, lookupVocabularyLines, objectNoteLine, objectOnCuratedEventMessage, objectRecordedLine, warnLine, webhookNoteLines, } from "./messages.js";
30
+ import { PREPARE_COMMAND, PrepareRefusal, byoaListingFailedMessage, byoaRecordedLine, byoaRequiredMessage, describeFallbackWarning, generatedModulesLine, eventAllObjectsMessage, eventNextLine, eventParentFieldsMessage, eventSubscription, eventSummaryLine, filterableFieldsLine, genericEventNeedsObjectMessage, lookupVocabularyLines, objectNoteLine, objectOnCuratedEventMessage, objectRecordedLine, warnLine, webhookNoteLines, } from "./messages.js";
30
31
  import { RESOLUTIONS_FILENAME, matchObject, recordObjectResolution } from "./objects.js";
31
32
  import { lookupSpecsFromOverlay, mergeFilesInto } from "./overlay.js";
32
33
  import { regenerateDescriptors } from "./regenerate.js";
@@ -62,12 +63,15 @@ export function assembleEventDefinition(node, entry, describe, objectRow, eventN
62
63
  const objectParameters = objectRow === undefined ? null : pyGet(objectRow, "Parameters", null);
63
64
  let stored;
64
65
  if (describe !== undefined) {
65
- stored = mergeDescribeIntoNode(node, describe, objectParameters);
66
+ stored = mergeDescribeIntoNode(node, describe, objectParameters, objectRow ?? null);
66
67
  }
67
68
  else {
68
69
  stored = { ...node };
69
70
  const declared = pyOr(get(stored, "eventParameters"), {});
70
71
  stored.eventParameters = { fields: unionEventParameters(get(declared, "fields"), objectParameters) };
72
+ // The row's facts win here too: this definition IS the overlay, and a
73
+ // describe outage on a BYOA event must not store the registry's `false`.
74
+ Object.assign(stored, resolveEventFacts(objectRow ?? null, stored));
71
75
  }
72
76
  const out = { ...entry };
73
77
  const outputDefinition = pyOr(get(stored, "outputResponseDefinition"), {});
@@ -88,6 +92,9 @@ export function assembleEventDefinition(node, entry, describe, objectRow, eventN
88
92
  * connection into `bindings.json` by the time the event's own metadata says it
89
93
  * is the wrong KIND, so the file is restored first. A listing that FAILED is
90
94
  * not a tenant with no BYOA connections; the remedies are opposite.
95
+ *
96
+ * Returns the connection's BYOA listing row when it passes, for the record
97
+ * `prepare` then writes (see `byoa-ledger.ts`).
91
98
  */
92
99
  export async function refuseUnlessByoaConnection(client, connectorKey, connectionId, rollback, warn) {
93
100
  const restore = () => {
@@ -104,8 +111,9 @@ export async function refuseUnlessByoaConnection(client, connectorKey, connectio
104
111
  restore();
105
112
  throw new PrepareRefusal(byoaListingFailedMessage(connectionId, error), "BYOA_LISTING_FAILED");
106
113
  }
107
- if (rows.some((row) => pyStrAny(pyGet(row, "Id", null)) === connectionId))
108
- return;
114
+ const match = rows.find((row) => pyStrAny(pyGet(row, "Id", null)) === connectionId);
115
+ if (match)
116
+ return match;
109
117
  restore();
110
118
  throw new PrepareRefusal(byoaRequiredMessage(connectionId, connectorKey, byoaListing(rows, connectorKey)), "BYOA_REQUIRED");
111
119
  }
@@ -212,14 +220,20 @@ export async function prepareEvent(ctx, options) {
212
220
  }
213
221
  }
214
222
  const facts = eventFacts(node, objectRow ?? null);
215
- if (facts.byoaConnection) {
216
- await refuseUnlessByoaConnection(ctx.client, connectorKey, connectionId, options.bindings, warn);
217
- }
223
+ const byoaRow = facts.byoaConnection
224
+ ? await refuseUnlessByoaConnection(ctx.client, connectorKey, connectionId, options.bindings, warn)
225
+ : undefined;
218
226
  const definition = assembleEventDefinition(node, entry, describe, objectRow, eventNodeType);
219
227
  const plan = planLibrary([definition.entry], () => definition.node);
220
228
  for (const line of plan.warnings)
221
229
  warn(line);
222
230
  mergeFilesInto(plan.files, out);
231
+ if (byoaRow !== undefined) {
232
+ // What `check` and `compile` look for before they accept this event on this
233
+ // connection: the proof that the live check above ran and passed.
234
+ recordByoaVerification(out, eventNodeType, connectionId, byoaRow);
235
+ info(byoaRecordedLine(connectionId, pyPathJoin(outShown, RESOLUTIONS_FILENAME)));
236
+ }
223
237
  if (generic) {
224
238
  recordObjectResolution(out, eventNodeType, requestedObject, objectRow, operation);
225
239
  // Named as the author spelled `--out`, not as the file was resolved.
@@ -237,6 +237,8 @@ export declare function warnLine(error: unknown): string;
237
237
  /** The describe could not answer for a curated event; the definition's own sections stand in. */
238
238
  export declare function describeFallbackWarning(error: unknown, bakedObject: string): string;
239
239
  export declare function byoaListingFailedMessage(connectionId: string, error: unknown): string;
240
+ /** The passing BYOA check, and where it was recorded for `check` / `compile`. */
241
+ export declare function byoaRecordedLine(connectionId: string, ledgerPath: string): string;
240
242
  export declare function byoaRequiredMessage(connectionId: string, connectorKey: string, listing: string): string;
241
243
  export declare function eventSummaryLine(eventNodeType: string, out: string, operation: string, mode: string, whereParameters: number, filterFields: number): string;
242
244
  /** The first eight filterable field names, or nothing when the event has none. */
@@ -548,6 +548,10 @@ export function byoaListingFailedMessage(connectionId, error) {
548
548
  "(bring-your-own-app) connection, and the BYOA listing needed to " +
549
549
  `check ${connectionId} against it failed: ${messageOf(error)}`);
550
550
  }
551
+ /** The passing BYOA check, and where it was recorded for `check` / `compile`. */
552
+ export function byoaRecordedLine(connectionId, ledgerPath) {
553
+ return ` byoa: ${connectionId} is a BYOA connection — recorded in ${ledgerPath}`;
554
+ }
551
555
  export function byoaRequiredMessage(connectionId, connectorKey, listing) {
552
556
  return (`${PREFIX}this event object requires a BYOA ` +
553
557
  `(bring-your-own-app) connection, and ${connectionId} is not ` +