@uipath/maestro-builder-sdk 6.11.4 → 6.11.6

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.
@@ -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.
@@ -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
  *
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The queue item's own Orchestrator record — the fields `core.action.queue.create`
3
+ * publishes, declared so a downstream read is checkable. Shared by the serializer
4
+ * (which declares it on the node) and `check` (which refuses a read outside it).
5
+ *
6
+ * Unlike an rpa process's output arguments, this shape is NOT per-tenant: it is
7
+ * Orchestrator's queue-item record, the same for every queue, so the author has
8
+ * nothing to declare and `returns` is not needed for a fire-and-forget enqueue.
9
+ * The dispatcher returns `env.Data` from `uip or queue-items add` verbatim, and
10
+ * these are the fields a real add answered with (pinned 2026-07-28 against
11
+ * `Shared/SupplierInvoices` on codereval; `gen-tests`' fixture for the node is the
12
+ * same list). Declaring the EVIDENCED fields and no more is PR 10's payload-schema
13
+ * pattern — an invented field would be #73's `firedAt` again.
14
+ */
15
+ export declare const QUEUE_ITEM_RECORD_SCHEMA: {
16
+ readonly type: "object";
17
+ readonly properties: {
18
+ readonly Id: {
19
+ readonly type: "number";
20
+ };
21
+ readonly Key: {
22
+ readonly type: "string";
23
+ };
24
+ readonly UniqueKey: {
25
+ readonly type: "string";
26
+ };
27
+ readonly Reference: {
28
+ readonly type: "string";
29
+ };
30
+ readonly Status: {
31
+ readonly type: "string";
32
+ };
33
+ readonly Priority: {
34
+ readonly type: "string";
35
+ };
36
+ readonly QueueDefinitionName: {
37
+ readonly type: "string";
38
+ };
39
+ readonly QueueDefinitionKey: {
40
+ readonly type: "string";
41
+ };
42
+ readonly CreationTime: {
43
+ readonly type: "string";
44
+ };
45
+ readonly StartProcessing: {
46
+ readonly type: "string";
47
+ };
48
+ readonly EndProcessing: {
49
+ readonly type: "string";
50
+ };
51
+ readonly Progress: {
52
+ readonly type: "string";
53
+ };
54
+ readonly ReviewStatus: {
55
+ readonly type: "string";
56
+ };
57
+ readonly DueDate: {
58
+ readonly type: "string";
59
+ };
60
+ readonly RiskSlaDate: {
61
+ readonly type: "string";
62
+ };
63
+ readonly DeferDate: {
64
+ readonly type: "string";
65
+ };
66
+ readonly Encrypted: {
67
+ readonly type: "boolean";
68
+ };
69
+ readonly SpecificContent: {
70
+ readonly type: "object";
71
+ };
72
+ readonly Output: {
73
+ readonly type: "object";
74
+ };
75
+ readonly OutputData: {
76
+ readonly type: "string";
77
+ };
78
+ readonly Analytics: {
79
+ readonly type: "object";
80
+ };
81
+ readonly AnalyticsData: {
82
+ readonly type: "string";
83
+ };
84
+ readonly ProcessingExceptionType: {
85
+ readonly type: "string";
86
+ };
87
+ readonly ProcessingExceptionReason: {
88
+ readonly type: "string";
89
+ };
90
+ readonly SecondsInPreviousAttempts: {
91
+ readonly type: "number";
92
+ };
93
+ readonly RetryNumber: {
94
+ readonly type: "number";
95
+ };
96
+ readonly CreatorJobKey: {
97
+ readonly type: "string";
98
+ };
99
+ readonly ExecutorJobKey: {
100
+ readonly type: "string";
101
+ };
102
+ readonly RobotKey: {
103
+ readonly type: "string";
104
+ };
105
+ };
106
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The queue item's own Orchestrator record — the fields `core.action.queue.create`
3
+ * publishes, declared so a downstream read is checkable. Shared by the serializer
4
+ * (which declares it on the node) and `check` (which refuses a read outside it).
5
+ *
6
+ * Unlike an rpa process's output arguments, this shape is NOT per-tenant: it is
7
+ * Orchestrator's queue-item record, the same for every queue, so the author has
8
+ * nothing to declare and `returns` is not needed for a fire-and-forget enqueue.
9
+ * The dispatcher returns `env.Data` from `uip or queue-items add` verbatim, and
10
+ * these are the fields a real add answered with (pinned 2026-07-28 against
11
+ * `Shared/SupplierInvoices` on codereval; `gen-tests`' fixture for the node is the
12
+ * same list). Declaring the EVIDENCED fields and no more is PR 10's payload-schema
13
+ * pattern — an invented field would be #73's `firedAt` again.
14
+ */
15
+ export const QUEUE_ITEM_RECORD_SCHEMA = {
16
+ type: 'object',
17
+ properties: {
18
+ Id: { type: 'number' },
19
+ Key: { type: 'string' },
20
+ UniqueKey: { type: 'string' },
21
+ Reference: { type: 'string' },
22
+ Status: { type: 'string' },
23
+ Priority: { type: 'string' },
24
+ QueueDefinitionName: { type: 'string' },
25
+ QueueDefinitionKey: { type: 'string' },
26
+ CreationTime: { type: 'string' },
27
+ StartProcessing: { type: 'string' },
28
+ EndProcessing: { type: 'string' },
29
+ Progress: { type: 'string' },
30
+ ReviewStatus: { type: 'string' },
31
+ DueDate: { type: 'string' },
32
+ RiskSlaDate: { type: 'string' },
33
+ DeferDate: { type: 'string' },
34
+ Encrypted: { type: 'boolean' },
35
+ SpecificContent: { type: 'object' },
36
+ Output: { type: 'object' },
37
+ OutputData: { type: 'string' },
38
+ Analytics: { type: 'object' },
39
+ AnalyticsData: { type: 'string' },
40
+ ProcessingExceptionType: { type: 'string' },
41
+ ProcessingExceptionReason: { type: 'string' },
42
+ SecondsInPreviousAttempts: { type: 'number' },
43
+ RetryNumber: { type: 'number' },
44
+ CreatorJobKey: { type: 'string' },
45
+ ExecutorJobKey: { type: 'string' },
46
+ RobotKey: { type: 'string' },
47
+ },
48
+ };
@@ -974,7 +974,7 @@ declare class StepList {
974
974
  * **Paginate until the API says stop**
975
975
  * ```ts
976
976
  * .var('page', types.number, 1)
977
- * .doWhile('paginate', js`$vars.fetch.output.hasNextPage === true`, (b) => b
977
+ * .doWhile('paginate', js`$vars.fetch.output.body.hasNextPage === true`, (b) => b
978
978
  * .step('fetch', http({ url: tmpl`https://api.example.test/items?page=${v('page')}`, method: 'GET', managed: false, returns: { hasNextPage: 'boolean' } }), {
979
979
  * updates: { page: js`$vars.page + 1` },
980
980
  * }), { limit: 50 })
package/dist/flow-sdk.js CHANGED
@@ -709,7 +709,7 @@ class StepList {
709
709
  * **Paginate until the API says stop**
710
710
  * ```ts
711
711
  * .var('page', types.number, 1)
712
- * .doWhile('paginate', js`$vars.fetch.output.hasNextPage === true`, (b) => b
712
+ * .doWhile('paginate', js`$vars.fetch.output.body.hasNextPage === true`, (b) => b
713
713
  * .step('fetch', http({ url: tmpl`https://api.example.test/items?page=${v('page')}`, method: 'GET', managed: false, returns: { hasNextPage: 'boolean' } }), {
714
714
  * updates: { page: js`$vars.page + 1` },
715
715
  * }), { limit: 50 })
@@ -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 {};