@uipath/maestro-builder-sdk 6.41.2 → 6.43.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 (73) hide show
  1. package/dist/api-index.md +99 -99
  2. package/dist/api-members.md +307 -307
  3. package/dist/check.d.ts +0 -19
  4. package/dist/check.js +40 -1036
  5. package/dist/cli/commands/registry.js +15 -0
  6. package/dist/compile-cli.js +2 -1
  7. package/dist/core/actions.d.ts +1 -713
  8. package/dist/core/actions.js +0 -193
  9. package/dist/core/agent-sidecar.d.ts +186 -0
  10. package/dist/core/agent-sidecar.js +409 -0
  11. package/dist/core/agent-tool-resources.d.ts +62 -0
  12. package/dist/core/agent-tool-resources.js +81 -0
  13. package/dist/core/cli-spelling.d.ts +9 -0
  14. package/dist/core/cli-spelling.js +9 -0
  15. package/dist/{config.js → core/connector-configuration.js} +2 -2
  16. package/dist/core/library-cache.d.ts +0 -9
  17. package/dist/core/library-cache.js +0 -9
  18. package/dist/core/library.d.ts +47 -0
  19. package/dist/core/library.js +135 -34
  20. package/dist/core/list-envelope.d.ts +36 -1
  21. package/dist/core/list-envelope.js +24 -19
  22. package/dist/core/lookup-store.d.ts +4 -3
  23. package/dist/core/lookup-store.js +46 -38
  24. package/dist/core/node-id-placeholder.d.ts +8 -0
  25. package/dist/core/node-id-placeholder.js +10 -0
  26. package/dist/core/read-text.d.ts +26 -0
  27. package/dist/core/read-text.js +43 -0
  28. package/dist/core/ts-literal.d.ts +6 -0
  29. package/dist/core/ts-literal.js +15 -0
  30. package/dist/core/typed-raw-node.js +0 -5
  31. package/dist/decompile.js +34 -822
  32. package/dist/flow-node-registry.js +2 -1
  33. package/dist/flow-node-type.d.ts +130 -6
  34. package/dist/flow-sdk.d.ts +1 -0
  35. package/dist/flow-sdk.js +1 -0
  36. package/dist/nodes/agent/flow/check.d.ts +88 -0
  37. package/dist/nodes/agent/flow/check.js +873 -0
  38. package/dist/nodes/agent/flow/factories.d.ts +134 -0
  39. package/dist/nodes/agent/flow/factories.js +143 -0
  40. package/dist/nodes/agent/flow/family.d.ts +3 -0
  41. package/dist/nodes/agent/flow/family.js +8 -0
  42. package/dist/nodes/agent/flow/index.d.ts +3 -0
  43. package/dist/nodes/agent/flow/index.js +3 -0
  44. package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
  45. package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
  46. package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
  47. package/dist/nodes/agent/flow/inline-agent.js +526 -0
  48. package/dist/nodes/agent/flow/node-type.d.ts +20 -0
  49. package/dist/nodes/agent/flow/node-type.js +422 -0
  50. package/dist/nodes/agent/flow/resources.d.ts +502 -0
  51. package/dist/nodes/agent/flow/resources.js +1409 -0
  52. package/dist/nodes/agent/flow/spec.d.ts +448 -0
  53. package/dist/nodes/agent/flow/spec.js +1 -0
  54. package/dist/nodes/process/flow/check.d.ts +40 -2
  55. package/dist/nodes/process/flow/check.js +156 -0
  56. package/dist/nodes/process/flow/factories.d.ts +58 -3
  57. package/dist/nodes/process/flow/factories.js +59 -2
  58. package/dist/nodes/process/flow/node-type.d.ts +4 -1
  59. package/dist/nodes/process/flow/node-type.js +181 -10
  60. package/dist/nodes/process/flow/spec.d.ts +112 -5
  61. package/dist/registry/describe-cli.d.ts +10 -0
  62. package/dist/registry/describe-cli.js +41 -0
  63. package/dist/registry/library-reader.d.ts +12 -2
  64. package/dist/registry/library-reader.js +17 -16
  65. package/dist/registry/lookup-specs.d.ts +17 -1
  66. package/dist/registry/lookup-specs.js +3 -7
  67. package/dist/registry/path-cli.js +2 -1
  68. package/dist/registry/search-cli.js +2 -1
  69. package/dist/serialize.d.ts +9 -54
  70. package/dist/serialize.js +47 -2763
  71. package/package.json +2 -2
  72. /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
  73. /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
@@ -1,3 +1,12 @@
1
+ /**
2
+ * library — resolve Integration Service connector definitions from the on-disk
3
+ * connector library cache (the same `library-json` the Flow Code arm uses).
4
+ *
5
+ * For a connector action we need two files per action:
6
+ * <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
7
+ * <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
8
+ */
9
+ import { type ReadText } from './read-text.js';
1
10
  import { type LookupResolutions, type LookupSpec } from './lookups.js';
2
11
  import type { ByoaEventFacts, ByoaLedger } from './byoa.js';
3
12
  export interface ResolvedConnector {
@@ -217,8 +226,27 @@ export interface LibraryOpts {
217
226
  * library exposed (a thin `describe` response must not shrink what compiles).
218
227
  */
219
228
  overlayDir?: string;
229
+ /** Reads each library and overlay file. Defaults to the disk; a host without one passes its own. */
230
+ readText?: ReadText;
231
+ /** Receives the library's warnings. Defaults to stderr; a host without `process` passes its own. */
232
+ warn?: (message: string) => void;
220
233
  }
221
234
  export { ConnectorResolveError, type ConnectorResolveCode } from './connector-resolve-error.js';
235
+ /** What `registry describe` renders one operation's page from. */
236
+ export interface PageSource {
237
+ entry: any;
238
+ /** The Flow definition beside the entry, or `undefined` when there is none. */
239
+ v1def: any;
240
+ version: string;
241
+ /** True when the operation came from the prepared `connectors-local` overlay. */
242
+ prepared: boolean;
243
+ /** The folder the operation came from. */
244
+ dir: string;
245
+ /** That layer's index rows. The page names the operation's symbol from them. */
246
+ rows: readonly any[];
247
+ /** `objectName`s the ledgers declare connector-invariant. */
248
+ invariant: ReadonlySet<string>;
249
+ }
222
250
  export declare class Library {
223
251
  private layers;
224
252
  /** The LIST-ENVELOPE LEDGER, overlay first so a prepared entry can add a row. */
@@ -239,6 +267,8 @@ export declare class Library {
239
267
  readonly byoaVerifications: ByoaLedger;
240
268
  /** Collections the ledger declares connector-invariant; overlay unions over baked. */
241
269
  private readonly invariantCollections;
270
+ private readonly read;
271
+ private readonly warn;
242
272
  constructor(dir: string, opts?: LibraryOpts);
243
273
  /**
244
274
  * Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
@@ -267,6 +297,23 @@ export declare class Library {
267
297
  */
268
298
  lookupSpecs(key: string, action: string, version?: string, objectName?: string): Record<string, LookupSpec>;
269
299
  resolve(key: string, action: string, version?: string, objectName?: string): ResolvedConnector;
300
+ /**
301
+ * One operation's page source, for `registry describe`: the entry and definition
302
+ * of one index row, from the first layer that carries it. That is the prepared
303
+ * overlay before the library, the order `resolve` reads in. Without a version, the
304
+ * newest. A generic operation has a row per object, as `registry pull` writes a
305
+ * page per object: `objectName` picks the row; without it, the object-less row is
306
+ * used when there is one, else CONNECTOR_OBJECT_REQUIRED lists the objects. Rows are
307
+ * chosen as `resolveInLayer` chooses them, so a page shows only what `compile` accepts.
308
+ */
309
+ pageSource(nodeType: string, version?: string, objectName?: string): PageSource;
310
+ /**
311
+ * The row an object the layer does not list reads its page from, as `resolveInLayer`
312
+ * case 1 accepts it: the one object-less row of an un-expanded generic operation.
313
+ * A single non-generic operation takes no object at all.
314
+ */
315
+ private connectionScopedRow;
316
+ private pageOf;
270
317
  /**
271
318
  * Whether a connector step's schema is materialized locally — see
272
319
  * {@link SchemaReadiness}. Index rows are enough for the `object` question
@@ -6,10 +6,8 @@
6
6
  * <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
7
7
  * <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
8
8
  */
9
- import { readFileSync, existsSync } from 'node:fs';
10
- import { join } from 'node:path';
11
- import { prepareCommand } from './cli-spelling.js';
12
- import { PULL_COMMAND } from './library-cache.js';
9
+ import { cacheFor, libraryPath, readJson, readTextFromDisk } from './read-text.js';
10
+ import { PULL_COMMAND, prepareCommand } from './cli-spelling.js';
13
11
  import { editDistance } from './edit-distance.js';
14
12
  import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
15
13
  import { lookupSpecOf } from './lookups.js';
@@ -37,26 +35,35 @@ export class Library {
37
35
  byoaVerifications;
38
36
  /** Collections the ledger declares connector-invariant; overlay unions over baked. */
39
37
  invariantCollections;
38
+ read;
39
+ warn;
40
40
  constructor(dir, opts = {}) {
41
- this.lookupResolutions = loadLookupResolutions(opts.overlayDir);
42
- this.byoaVerifications = loadByoaLedger(opts.overlayDir);
41
+ const read = (this.read = opts.readText ?? readTextFromDisk);
42
+ this.warn = opts.warn ?? ((message) => { globalThis.process?.stderr?.write(message); });
43
+ // A new identity per Library, so the per-reader caches never keep an overlay file: `prepare` rewrites them.
44
+ const readOverlay = (path) => read(path);
45
+ this.lookupResolutions = loadLookupResolutions(opts.overlayDir, readOverlay);
46
+ this.byoaVerifications = loadByoaLedger(opts.overlayDir, readOverlay);
43
47
  this.invariantCollections = new Set([
44
- ...loadInvariantCollections(dir),
45
- ...loadInvariantCollections(opts.overlayDir),
48
+ ...loadInvariantCollections(dir, read),
49
+ ...loadInvariantCollections(opts.overlayDir, readOverlay),
46
50
  ]);
47
51
  // Overlay first (local wins), then the baked library.
48
52
  if (opts.overlayDir) {
49
- const oidx = join(opts.overlayDir, 'index.json');
50
- if (existsSync(oidx))
51
- this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(readFileSync(oidx, 'utf8')).entries ?? [], overlay: true });
52
- this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir));
53
+ const overlayIndex = readOverlay(libraryPath(opts.overlayDir, 'index.json'));
54
+ if (overlayIndex !== undefined)
55
+ this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(overlayIndex).entries ?? [], overlay: true });
56
+ this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir, readOverlay));
53
57
  }
54
- this.envelopeRows.push(...loadListEnvelopes(dir));
55
- const idx = join(dir, 'index.json');
56
- if (!existsSync(idx))
57
- throw new Error(`connector library not found at ${idx}. Build it first (uipath-flow-v2-setup-library) ` +
58
- `or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`);
59
- this.layers.push({ dir, entries: JSON.parse(readFileSync(idx, 'utf8')).entries ?? [] });
58
+ this.envelopeRows.push(...loadListEnvelopes(dir, read));
59
+ const idx = libraryPath(dir, 'index.json');
60
+ const entries = libraryEntries(read, idx);
61
+ if (entries === undefined)
62
+ throw new Error(`connector library not found at ${idx}. `
63
+ + (read === readTextFromDisk
64
+ ? `Run \`${PULL_COMMAND}\`, or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`
65
+ : 'The reader passed to Library has no such file.'));
66
+ this.layers.push({ dir, entries });
60
67
  }
61
68
  /**
62
69
  * Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
@@ -102,6 +109,83 @@ export class Library {
102
109
  resolve(key, action, version, objectName) {
103
110
  return this.resolveType(`uipath.connector.${key}.${action}`, version, objectName);
104
111
  }
112
+ /**
113
+ * One operation's page source, for `registry describe`: the entry and definition
114
+ * of one index row, from the first layer that carries it. That is the prepared
115
+ * overlay before the library, the order `resolve` reads in. Without a version, the
116
+ * newest. A generic operation has a row per object, as `registry pull` writes a
117
+ * page per object: `objectName` picks the row; without it, the object-less row is
118
+ * used when there is one, else CONNECTOR_OBJECT_REQUIRED lists the objects. Rows are
119
+ * chosen as `resolveInLayer` chooses them, so a page shows only what `compile` accepts.
120
+ */
121
+ pageSource(nodeType, version, objectName) {
122
+ const layers = this.layers.flatMap((layer) => {
123
+ const matches = layer.entries.filter((e) => e?.nodeType === nodeType && (!version || String(e.version) === version)
124
+ && typeof e.path === 'string' && e.path.endsWith('.json'));
125
+ if (!matches.length)
126
+ return [];
127
+ const newest = matches.map((e) => String(e.version))
128
+ .reduce((a, b) => (b.localeCompare(a, undefined, { numeric: true }) > 0 ? b : a));
129
+ return [{ layer, rows: matches.filter((e) => String(e.version) === newest) }];
130
+ });
131
+ if (!layers.length) {
132
+ throw new ConnectorResolveError('CONNECTOR_NOT_IN_LIBRARY', `connector not in library: ${nodeType}${version ? '@' + version : ''}.`
133
+ + this.describeMiss(nodeType, version, undefined, false));
134
+ }
135
+ const objects = [...new Set(layers.flatMap(({ rows }) => rows.map((e) => e.objectName)
136
+ .filter((o) => typeof o === 'string' && o !== '')))].sort();
137
+ // `compile` refuses a missing object when any layer holds several rows with no one template row.
138
+ if (objectName === undefined && layers.some(({ rows }) => rows.length > 1 && rows.filter((e) => !e.objectName).length !== 1)) {
139
+ throw new ConnectorResolveError('CONNECTOR_OBJECT_REQUIRED', `connector ${nodeType} is a GENERIC operation covering ${objects.length} objects — `
140
+ + `say which one with --object, e.g. --object ${objects[0] ?? '<object>'}. ${describeObjects(objects)}`);
141
+ }
142
+ for (const { layer, rows } of layers) {
143
+ const row = objectName === undefined
144
+ ? (rows.length === 1 ? rows[0] : rows.find((e) => !e.objectName))
145
+ : rows.find((e) => e.objectName === objectName) ?? this.connectionScopedRow(nodeType, rows, objectName);
146
+ if (row)
147
+ return this.pageOf(nodeType, layer, row);
148
+ }
149
+ const parts = nodeType.split('.');
150
+ const eventNs = parts[2] === 'trigger' || parts[2] === 'event';
151
+ const near = nearest(objectName ?? '', objects);
152
+ throw new ConnectorResolveError('CONNECTOR_NOT_IN_LIBRARY', `connector ${nodeType} has no page for object "${objectName}"`
153
+ + `${near ? ` (nearest: "${near}")` : ''}. ${describeObjects(objects)} If it is a connection-scoped object, run `
154
+ + `\`${prepareCommand(eventNs ? parts[3] : parts[2], (eventNs ? parts.slice(4) : parts.slice(3)).join('.'), '--object', String(objectName))}\` `
155
+ + 'to prepare it, then describe again.');
156
+ }
157
+ /**
158
+ * The row an object the layer does not list reads its page from, as `resolveInLayer`
159
+ * case 1 accepts it: the one object-less row of an un-expanded generic operation.
160
+ * A single non-generic operation takes no object at all.
161
+ */
162
+ connectionScopedRow(nodeType, rows, objectName) {
163
+ if (rows.length !== 1)
164
+ return undefined;
165
+ const only = rows[0];
166
+ if (only.activityType === 'Generic' || only.activityType === 'GenericTrigger' || only.objectFromDiscovery === true) {
167
+ return only.objectName ? undefined : only;
168
+ }
169
+ throw new ConnectorResolveError('CONNECTOR_OBJECT_NOT_GENERIC', `connector ${nodeType} does not have an object "${objectName}": it is a single, `
170
+ + `non-generic operation addressing ${only.objectName ? `"${only.objectName}"` : 'no object'}. `
171
+ + 'Drop --object (it is only for generic operations, whose node type covers many objects).');
172
+ }
173
+ pageOf(nodeType, layer, row) {
174
+ const entryPath = libraryPath(layer.dir, row.path);
175
+ const entry = readLibraryFile(nodeType, entryPath, () => readJson(this.read, entryPath));
176
+ const sidecarPath = v1defPathOf(entryPath);
177
+ const sidecar = this.read(sidecarPath);
178
+ const raw = sidecar === undefined ? undefined : readLibraryFile(nodeType, sidecarPath, () => JSON.parse(sidecar));
179
+ return {
180
+ entry,
181
+ v1def: Array.isArray(raw) ? raw[0] : raw,
182
+ version: String(row.version),
183
+ prepared: layer.overlay === true,
184
+ dir: layer.dir,
185
+ rows: layer.entries,
186
+ invariant: this.invariantCollections,
187
+ };
188
+ }
105
189
  /**
106
190
  * Whether a connector step's schema is materialized locally — see
107
191
  * {@link SchemaReadiness}. Index rows are enough for the `object` question
@@ -158,7 +242,7 @@ export class Library {
158
242
  let entry;
159
243
  try {
160
244
  const source = rows.find(({ overlay }) => !overlay) ?? rows[0];
161
- entry = JSON.parse(readFileSync(join(source.dir, source.row.path), 'utf8'));
245
+ entry = readJson(this.read, libraryPath(source.dir, source.row.path));
162
246
  }
163
247
  catch {
164
248
  return undefined;
@@ -191,7 +275,7 @@ export class Library {
191
275
  continue;
192
276
  if (wire.version && indexed.version !== wire.version)
193
277
  continue;
194
- const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
278
+ const entry = readJson(this.read, libraryPath(layer.dir, indexed.path));
195
279
  const operation = entry.operation ?? {};
196
280
  if (wire.httpMethod && String(operation.httpMethod ?? '').toUpperCase() !== wire.httpMethod.toUpperCase())
197
281
  continue;
@@ -289,8 +373,9 @@ export class Library {
289
373
  continue;
290
374
  if (wire.version && indexed.version !== wire.version)
291
375
  continue;
292
- const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
293
- const operation = normalizeEventOperation({ entry, v1def: readV1def(join(layer.dir, indexed.path)) }).entry.operation ?? {};
376
+ const entryPath = libraryPath(layer.dir, indexed.path);
377
+ const entry = readJson(this.read, entryPath);
378
+ const operation = normalizeEventOperation({ entry, v1def: readV1def(this.read, entryPath) }).entry.operation ?? {};
294
379
  // A GENERIC event's entry names no object — the wire's object is the
295
380
  // author's choice, so it identifies nothing here and comes back as `object`.
296
381
  const generic = operation.activityType === 'GenericTrigger';
@@ -600,7 +685,7 @@ export class Library {
600
685
  * `connector not in library: …send-mail-v2.` said nothing that would reveal
601
686
  * that. It resorted to grepping the library's JSON out of `node_modules`.
602
687
  */
603
- describeMiss(nodeType, version, objectName) {
688
+ describeMiss(nodeType, version, objectName, searched = true) {
604
689
  // `uipath.connector.<key>.<op>`, or `uipath.connector.(trigger|event).<key>.<ev>`
605
690
  // for the two event namespaces. No connector key contains a dot.
606
691
  const parts = nodeType.split('.');
@@ -609,7 +694,8 @@ export class Library {
609
694
  const asked = (eventNs ? parts.slice(4) : parts.slice(3)).join('.');
610
695
  if (!key)
611
696
  return '';
612
- const where = ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.`;
697
+ // `describe` names no folder: on a browser host it is a store the agent cannot open.
698
+ const where = searched ? ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.` : '';
613
699
  if (eventNs)
614
700
  return this.describeEventMiss(parts[2], key, asked, where, version, objectName);
615
701
  const ops = this.opsUnder(`uipath.connector.${key}.`);
@@ -753,7 +839,7 @@ export class Library {
753
839
  return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
754
840
  }
755
841
  if (only.activityType === 'Generic' || only.objectFromDiscovery === true) {
756
- process.stderr.write(` warn: connector ${nodeType}: the library carries this operation un-expanded `
842
+ this.warn(` warn: connector ${nodeType}: the library carries this operation un-expanded `
757
843
  + `(one object-less entry). Object "${objectName}" is accepted as a connection-scoped `
758
844
  + `object — the schema comes from the generic entry and the tenant resolves the name at `
759
845
  + `dispatch. If it is a typo, compile will not catch it.\n`);
@@ -784,7 +870,7 @@ export class Library {
784
870
  // sibling whose path template is exactly `/<its object>` — the only shape a
785
871
  // substitution can re-point cleanly.
786
872
  const near = nearest(objectName, objects);
787
- process.stderr.write(` warn: connector ${nodeType}: object "${objectName}" is not one of the `
873
+ this.warn(` warn: connector ${nodeType}: object "${objectName}" is not one of the `
788
874
  + `${objects.length} the library lists for it${near ? ` (nearest: "${near}")` : ''}. `
789
875
  + `Accepted as a connection-scoped object — the schema comes from a sibling entry and the `
790
876
  + `tenant resolves the name at dispatch. If it is a typo, compile will not catch it.\n`);
@@ -800,7 +886,7 @@ export class Library {
800
886
  pickDerivationSibling(dir, matches) {
801
887
  const templateOf = (e) => {
802
888
  try {
803
- const entry = JSON.parse(readFileSync(join(dir, e.path), 'utf8'));
889
+ const entry = readJson(this.read, libraryPath(dir, e.path));
804
890
  return entry.operation?.pathTemplate ?? entry.operation?.path ?? '';
805
891
  }
806
892
  catch {
@@ -814,14 +900,14 @@ export class Library {
814
900
  return substitutable ?? matches[0];
815
901
  }
816
902
  build(dir, e, nodeType, opts = {}) {
817
- const entryPath = join(dir, e.path);
903
+ const entryPath = libraryPath(dir, e.path);
818
904
  // The two files the index row promises. A missing or corrupt one is a fault in
819
905
  // the library on disk, not in the author's source, but compile cannot go on
820
906
  // without it, so it is a typed refusal like the others: `check` reports it with
821
907
  // the same code and text instead of passing a step compile then dies on with a
822
908
  // bare ENOENT (#1015).
823
- const entry = readLibraryFile(nodeType, entryPath, () => JSON.parse(readFileSync(entryPath, 'utf8')));
824
- const v1def = readLibraryFile(nodeType, v1defPathOf(entryPath), () => readV1def(entryPath));
909
+ const entry = readLibraryFile(nodeType, entryPath, () => readJson(this.read, entryPath));
910
+ const v1def = readLibraryFile(nodeType, v1defPathOf(entryPath), () => readV1def(this.read, entryPath));
825
911
  // A stamped object (case 4) reaches here with the index entry's `objectName`
826
912
  // replaced but the sibling's FILE on disk — so restate the object and derive
827
913
  // the endpoint from the sibling's own template, rather than shipping a node
@@ -846,7 +932,7 @@ export class Library {
846
932
  && entry.operation.objectName !== e.objectName) {
847
933
  const stale = entry.operation.objectName;
848
934
  const derived = deriveObjectPath(entry.operation, e.objectName);
849
- process.stderr.write(` warn: connector ${nodeType}: the library index says objectName "${e.objectName}" but its `
935
+ this.warn(` warn: connector ${nodeType}: the library index says objectName "${e.objectName}" but its `
850
936
  + `definition file says "${stale}". Using the index and deriving the endpoint`
851
937
  + (derived.path ? ` (${entry.operation.path} -> ${derived.path})` : '')
852
938
  + `; the definition is the stale side when these disagree.\n`);
@@ -950,13 +1036,28 @@ function v1defPathOf(entryPath) {
950
1036
  * damaged after it was built: an interrupted pull, a hand-edited overlay. The
951
1037
  * remedy is the same either way: rebuild the layer the file belongs to.
952
1038
  */
1039
+ /** Each host reader's parsed library index. A library build does not change; the disk default still reads afresh. */
1040
+ const INDEX_CACHE = new WeakMap();
1041
+ /** The `entries` of the index at `path`, or `undefined` when there is none. */
1042
+ function libraryEntries(read, path) {
1043
+ const cache = read === readTextFromDisk ? undefined : cacheFor(INDEX_CACHE, read);
1044
+ const cached = cache?.get(path);
1045
+ if (cached)
1046
+ return cached;
1047
+ const text = read(path);
1048
+ if (text === undefined)
1049
+ return undefined;
1050
+ const entries = JSON.parse(text).entries ?? [];
1051
+ cache?.set(path, entries);
1052
+ return entries;
1053
+ }
953
1054
  function readLibraryFile(nodeType, path, read) {
954
1055
  try {
955
1056
  return read();
956
1057
  }
957
1058
  catch (error) {
958
1059
  const message = error instanceof Error ? error.message : String(error);
959
- // ENOENT already names the path; a JSON SyntaxError does not.
1060
+ // A read error already names the path; a JSON SyntaxError does not.
960
1061
  const reason = message.includes(path) ? message : `${path}: ${message}`;
961
1062
  throw new ConnectorResolveError('CONNECTOR_LIBRARY_ENTRY_UNREADABLE', `connector library entry for ${nodeType} is unreadable: ${reason}. The library index lists `
962
1063
  + `this operation, but the file is missing or not valid JSON, so the library on disk is incomplete. `
@@ -980,8 +1081,8 @@ function readLibraryFile(nodeType, path, read) {
980
1081
  * reported both its connection and `FolderKey` as "not configured" (UV-16774) while
981
1082
  * validate and debug — which fold the case — passed.
982
1083
  */
983
- function readV1def(entryPath) {
984
- const raw = JSON.parse(readFileSync(v1defPathOf(entryPath), 'utf8'));
1084
+ function readV1def(read, entryPath) {
1085
+ const raw = readJson(read, v1defPathOf(entryPath));
985
1086
  const v1def = Array.isArray(raw) ? raw[0] : raw;
986
1087
  const bindings = v1def?.model?.bindings;
987
1088
  if (typeof bindings?.resource === 'string' && bindings.resource.toLowerCase() === 'connection') {
@@ -1,3 +1,38 @@
1
+ /**
2
+ * list-envelope — read the LIST-ENVELOPE LEDGER that sits beside the connector
3
+ * library's `index.json`.
4
+ *
5
+ * ## What the ledger answers
6
+ *
7
+ * A connector LIST operation's node output is either the array itself (**bare**,
8
+ * `envelope: null`) or that array inside a one-field wrapper object
9
+ * (`envelope: "records"` ⇒ `{records: […]}`). **Nothing in the library declares
10
+ * which**: `outputSchema.fields` describes ONE record either way, and the two
11
+ * operations that declare no fields at all sit on opposite sides of what the
12
+ * corpus guessed ([#156](https://github.com/UiPath/flow-builder-sdk/issues/156)).
13
+ * It cannot be derived, so it is measured and written down — one row per
14
+ * operation, each carrying the evidence that put it there.
15
+ *
16
+ * ## The rule this file exists to enforce
17
+ *
18
+ * **An operation with no row is UNDECLARED, and undeclared means every reader
19
+ * behaves exactly as it did before the ledger existed.** The emitter declares no
20
+ * container, and a reader must go on treating an unknown name on a list output as
21
+ * a possible wrapper. 2,149 of the shipped library's 7,029 entries are List
22
+ * operations and the ledger holds eight — that ratio is the design, not a gap.
23
+ * It grows on demand, one measurement at a time.
24
+ *
25
+ * Measured 2026-08-02 (card G13, eight `flow debug` / product-runtime readings):
26
+ * every operation measured so far is **bare**, including both the corpus called
27
+ * wrappers. The ledger's own `$comment` carries the mechanism and the evidence
28
+ * tiers; this module only reads it.
29
+ *
30
+ * If a second reader is ever needed outside this package, copy this one rather
31
+ * than sharing it through a new package boundary: a 40-line JSON reader is the
32
+ * cheaper half. The LEDGER is the single source of truth — the readers are
33
+ * interchangeable.
34
+ */
35
+ import { type ReadText } from './read-text.js';
1
36
  /** One measured operation. `envelope: null` is the BARE array. */
2
37
  export interface ListEnvelopeRow {
3
38
  nodeType: string;
@@ -21,7 +56,7 @@ export interface ListEnvelopeRow {
21
56
  * an empty ledger — every operation undeclared — which is what a library built
22
57
  * before this card, or a bare fixture library, looks like.
23
58
  */
24
- export declare function loadListEnvelopes(libraryDir: string | undefined): ListEnvelopeRow[];
59
+ export declare function loadListEnvelopes(libraryDir: string | undefined, read?: ReadText): ListEnvelopeRow[];
25
60
  /**
26
61
  * The row for one operation, or `undefined` when it is undeclared.
27
62
  *
@@ -32,36 +32,41 @@
32
32
  * cheaper half. The LEDGER is the single source of truth — the readers are
33
33
  * interchangeable.
34
34
  */
35
- import { readFileSync, existsSync } from 'node:fs';
36
- import { join } from 'node:path';
37
- const CACHE = new Map();
35
+ import { cacheFor, libraryPath, readTextFromDisk } from './read-text.js';
36
+ const CACHE = new WeakMap();
38
37
  /**
39
38
  * Every declared row, from `<libraryDir>/list-envelopes.json`. An absent file is
40
39
  * an empty ledger — every operation undeclared — which is what a library built
41
40
  * before this card, or a bare fixture library, looks like.
42
41
  */
43
- export function loadListEnvelopes(libraryDir) {
42
+ export function loadListEnvelopes(libraryDir, read = readTextFromDisk) {
44
43
  if (!libraryDir)
45
44
  return [];
46
- const cached = CACHE.get(libraryDir);
45
+ const cache = cacheFor(CACHE, read);
46
+ const cached = cache.get(libraryDir);
47
47
  if (cached)
48
48
  return cached;
49
+ let text;
50
+ try {
51
+ text = read(libraryPath(libraryDir, 'list-envelopes.json'));
52
+ }
53
+ catch {
54
+ // Not cached: the reader may serve the file on a later call.
55
+ return [];
56
+ }
49
57
  let rows = [];
50
- const file = join(libraryDir, 'list-envelopes.json');
51
- if (existsSync(file)) {
52
- try {
53
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
54
- if (Array.isArray(parsed.operations))
55
- rows = parsed.operations;
56
- }
57
- catch {
58
- // A malformed ledger reads as an EMPTY one rather than throwing: the
59
- // permissive path is the pre-ledger behaviour, so the worst case of a bad
60
- // edit is losing the exactness, never breaking a compile.
61
- rows = [];
62
- }
58
+ try {
59
+ const parsed = JSON.parse(text ?? '{}');
60
+ if (Array.isArray(parsed.operations))
61
+ rows = parsed.operations;
62
+ }
63
+ catch {
64
+ // A malformed ledger reads as an EMPTY one rather than throwing: the
65
+ // permissive path is the pre-ledger behaviour, so the worst case of a bad
66
+ // edit is losing the exactness, never breaking a compile.
67
+ rows = [];
63
68
  }
64
- CACHE.set(libraryDir, rows);
69
+ cache.set(libraryDir, rows);
65
70
  return rows;
66
71
  }
67
72
  /**
@@ -1,3 +1,4 @@
1
+ import { type ReadText } from './read-text.js';
1
2
  import type { ByoaLedger } from './byoa.js';
2
3
  import type { LookupResolutions } from './lookups.js';
3
4
  /**
@@ -8,7 +9,7 @@ import type { LookupResolutions } from './lookups.js';
8
9
  * — it is a compile error naming the `prepare` command to run, which is the
9
10
  * behaviour an author wants from a missing resolution anyway.
10
11
  */
11
- export declare function loadLookupResolutions(overlayDir: string | undefined): LookupResolutions;
12
+ export declare function loadLookupResolutions(overlayDir: string | undefined, read?: ReadText): LookupResolutions;
12
13
  /**
13
14
  * Load the `byoa` section of `<overlayDir>/resolutions.json` — the BYOA checks
14
15
  * `registry prepare` recorded (see `core/byoa.ts`) — or an empty map.
@@ -19,7 +20,7 @@ export declare function loadLookupResolutions(overlayDir: string | undefined): L
19
20
  * call rather than cached: `prepare` rewrites this file, and a stale "never
20
21
  * checked" answer would refuse a flow that was just verified.
21
22
  */
22
- export declare function loadByoaLedger(overlayDir: string | undefined): ByoaLedger;
23
+ export declare function loadByoaLedger(overlayDir: string | undefined, read?: ReadText): ByoaLedger;
23
24
  /**
24
25
  * The invariant-collection ledger's `objectName` set, from a library dir.
25
26
  *
@@ -31,4 +32,4 @@ export declare function loadByoaLedger(overlayDir: string | undefined): ByoaLedg
31
32
  * @param libraryDir - A `library-json` directory, or the local overlay.
32
33
  * @returns Every `objectName` declared connector-invariant.
33
34
  */
34
- export declare function loadInvariantCollections(libraryDir: string | undefined): Set<string>;
35
+ export declare function loadInvariantCollections(libraryDir: string | undefined, read?: ReadText): Set<string>;
@@ -1,5 +1,4 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { cacheFor, libraryPath, readTextFromDisk } from './read-text.js';
3
2
  /**
4
3
  * core/lookup-store — the two on-disk side-tables the lookup surface reads.
5
4
  *
@@ -14,7 +13,7 @@ import { join } from 'node:path';
14
13
  * the CLIs need to read files. Keeping the split makes that structural rather
15
14
  * than a thing to remember.
16
15
  */
17
- const RESOLUTION_CACHE = new Map();
16
+ const RESOLUTION_CACHE = new WeakMap();
18
17
  /**
19
18
  * Load `<overlayDir>/resolutions.json`, or an empty map.
20
19
  *
@@ -23,26 +22,32 @@ const RESOLUTION_CACHE = new Map();
23
22
  * — it is a compile error naming the `prepare` command to run, which is the
24
23
  * behaviour an author wants from a missing resolution anyway.
25
24
  */
26
- export function loadLookupResolutions(overlayDir) {
25
+ export function loadLookupResolutions(overlayDir, read = readTextFromDisk) {
27
26
  if (!overlayDir)
28
27
  return {};
29
- const cached = RESOLUTION_CACHE.get(overlayDir);
28
+ const cache = cacheFor(RESOLUTION_CACHE, read);
29
+ const cached = cache.get(overlayDir);
30
30
  if (cached)
31
31
  return cached;
32
+ let text;
33
+ try {
34
+ text = read(libraryPath(overlayDir, 'resolutions.json'));
35
+ }
36
+ catch {
37
+ // Not cached: the reader may serve the file on a later call.
38
+ return {};
39
+ }
32
40
  let rows = {};
33
- const file = join(overlayDir, 'resolutions.json');
34
- if (existsSync(file)) {
35
- try {
36
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
37
- if (parsed.resolutions && typeof parsed.resolutions === 'object') {
38
- rows = parsed.resolutions;
39
- }
40
- }
41
- catch {
42
- rows = {};
41
+ try {
42
+ const parsed = JSON.parse(text ?? '{}');
43
+ if (parsed.resolutions && typeof parsed.resolutions === 'object') {
44
+ rows = parsed.resolutions;
43
45
  }
44
46
  }
45
- RESOLUTION_CACHE.set(overlayDir, rows);
47
+ catch {
48
+ rows = {};
49
+ }
50
+ cache.set(overlayDir, rows);
46
51
  return rows;
47
52
  }
48
53
  /**
@@ -55,14 +60,11 @@ export function loadLookupResolutions(overlayDir) {
55
60
  * call rather than cached: `prepare` rewrites this file, and a stale "never
56
61
  * checked" answer would refuse a flow that was just verified.
57
62
  */
58
- export function loadByoaLedger(overlayDir) {
63
+ export function loadByoaLedger(overlayDir, read = readTextFromDisk) {
59
64
  if (!overlayDir)
60
65
  return {};
61
- const file = join(overlayDir, 'resolutions.json');
62
- if (!existsSync(file))
63
- return {};
64
66
  try {
65
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
67
+ const parsed = JSON.parse(read(libraryPath(overlayDir, 'resolutions.json')) ?? '{}');
66
68
  const byoa = parsed?.byoa;
67
69
  return byoa && typeof byoa === 'object' && !Array.isArray(byoa) ? byoa : {};
68
70
  }
@@ -81,30 +83,36 @@ export function loadByoaLedger(overlayDir) {
81
83
  * @param libraryDir - A `library-json` directory, or the local overlay.
82
84
  * @returns Every `objectName` declared connector-invariant.
83
85
  */
84
- export function loadInvariantCollections(libraryDir) {
86
+ export function loadInvariantCollections(libraryDir, read = readTextFromDisk) {
85
87
  if (!libraryDir)
86
88
  return new Set();
87
- const cached = INVARIANT_CACHE.get(libraryDir);
89
+ const cache = cacheFor(INVARIANT_CACHE, read);
90
+ const cached = cache.get(libraryDir);
88
91
  if (cached)
89
92
  return cached;
93
+ let text;
94
+ try {
95
+ text = read(libraryPath(libraryDir, 'invariant-collections.json'));
96
+ }
97
+ catch {
98
+ // Not cached: the reader may serve the file on a later call.
99
+ return new Set();
100
+ }
90
101
  const out = new Set();
91
- const file = join(libraryDir, 'invariant-collections.json');
92
- if (existsSync(file)) {
93
- try {
94
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
95
- if (Array.isArray(parsed.collections)) {
96
- for (const row of parsed.collections) {
97
- const name = row?.objectName;
98
- if (typeof name === 'string')
99
- out.add(name);
100
- }
102
+ try {
103
+ const parsed = JSON.parse(text ?? '{}');
104
+ if (Array.isArray(parsed.collections)) {
105
+ for (const row of parsed.collections) {
106
+ const name = row?.objectName;
107
+ if (typeof name === 'string')
108
+ out.add(name);
101
109
  }
102
110
  }
103
- catch {
104
- /* an empty ledger — see above */
105
- }
106
111
  }
107
- INVARIANT_CACHE.set(libraryDir, out);
112
+ catch {
113
+ /* an empty ledger — see above */
114
+ }
115
+ cache.set(libraryDir, out);
108
116
  return out;
109
117
  }
110
- const INVARIANT_CACHE = new Map();
118
+ const INVARIANT_CACHE = new WeakMap();
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The `{nodeId}` placeholder a definition writes where a node's own id goes
3
+ * (an output's source, a model's context), filled in for one node. Every
4
+ * node's definition-derived fields go through this one rule, so an agent's
5
+ * outputs and every other node's cannot fill it differently.
6
+ */
7
+ /** `obj` with every `{nodeId}` in its JSON replaced by `nodeId`, as a fresh copy. */
8
+ export declare function subNodeId<T>(obj: T, nodeId: string): T;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The `{nodeId}` placeholder a definition writes where a node's own id goes
3
+ * (an output's source, a model's context), filled in for one node. Every
4
+ * node's definition-derived fields go through this one rule, so an agent's
5
+ * outputs and every other node's cannot fill it differently.
6
+ */
7
+ /** `obj` with every `{nodeId}` in its JSON replaced by `nodeId`, as a fresh copy. */
8
+ export function subNodeId(obj, nodeId) {
9
+ return JSON.parse(JSON.stringify(obj).split('{nodeId}').join(nodeId));
10
+ }