@uipath/maestro-builder-sdk 6.6.0 → 6.7.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.
package/dist/check.js CHANGED
@@ -116,7 +116,17 @@ function checkFlow(built, ancestors, opts = {}) {
116
116
  // decision waits until the expressions are collected (see
117
117
  // HITL_OUTCOMES_UNREACHABLE below).
118
118
  const hitlChoiceNoPorts = new Map();
119
- const dataFabricUpdates = [];
119
+ // Data Fabric writes that target a record `{ fromRead: '<step>' }` — update and
120
+ // delete share the selector, so they share the check.
121
+ const dataFabricRefs = [];
122
+ // Delete step names. `core.datafabric.delete` is the one verb of the family
123
+ // whose definition declares NO outputDefinition, so `$vars.<delete>.output` is
124
+ // undefined at run time (see DATAFABRIC_DELETE_HAS_NO_OUTPUT below).
125
+ const dataFabricDeletes = new Set();
126
+ // Reads that return a LIST. A multi-record read is refused as a write target
127
+ // (see DATAFABRIC_FROMREAD_MULTIPLE below) — the serializer emits a plain
128
+ // no-op task for the write, and nothing at any rung reports it.
129
+ const dataFabricMultiReads = new Set();
120
130
  // Placeholder (`mock()`) step names. The platform's `core.logic.mock` declares
121
131
  // one output whose `source` is the literal `"null"` — it publishes nothing —
122
132
  // so `$vars.<mock>.output` is null at run time, always (see
@@ -435,24 +445,52 @@ function checkFlow(built, ancestors, opts = {}) {
435
445
  });
436
446
  }
437
447
  }
438
- if (s.spec.kind === 'dataFabricRead' || s.spec.kind === 'dataFabricUpdate') {
448
+ if (s.spec.kind === 'dataFabricRead' || s.spec.kind === 'dataFabricCreate'
449
+ || s.spec.kind === 'dataFabricUpdate' || s.spec.kind === 'dataFabricDelete') {
439
450
  if (!String(s.spec.inputs.entity ?? '').trim()) {
440
451
  diags.push({
441
452
  level: 'error', code: 'DATAFABRIC_NO_ENTITY', step: s.name,
442
453
  message: `"${s.name}" names no Data Fabric entity. Set { entity: '<entity name>' }.`,
443
454
  });
444
455
  }
456
+ if (s.spec.kind === 'dataFabricRead') {
457
+ dataFabricReads.add(s.name);
458
+ if (s.spec.inputs.resultMode === 'multiple')
459
+ dataFabricMultiReads.add(s.name);
460
+ checkDataFabricQuery(s.name, s.spec.inputs, diags);
461
+ }
462
+ if (s.spec.kind === 'dataFabricCreate') {
463
+ // Not the same failure as an empty update: a BLANK value is omitted
464
+ // from the insert so the column default applies, so an all-blank
465
+ // `fieldValues` serializes to an empty body and runs green having
466
+ // inserted nothing. The node's schema rejects that — and the fix is
467
+ // a real value, never a placeholder to satisfy the rule.
468
+ const written = Object.values(s.spec.inputs.values ?? {})
469
+ .filter((v) => (v instanceof Expr ? true : String(v ?? '').trim() !== ''));
470
+ if (written.length === 0) {
471
+ diags.push({
472
+ level: 'error', code: 'DATAFABRIC_EMPTY_VALUES', step: s.name,
473
+ message: `"${s.name}" sets no column to a non-blank value, so it would insert nothing and run green. `
474
+ + 'The node\'s schema rejects that — give a real column a real value rather than a placeholder.',
475
+ });
476
+ }
477
+ checkDataFabricColumns(s.name, Object.keys(s.spec.inputs.values ?? {}), 'values', diags);
478
+ }
445
479
  if (s.spec.kind === 'dataFabricUpdate') {
446
- dataFabricUpdates.push({ step: s.name, inputs: s.spec.inputs });
447
480
  if (Object.keys(s.spec.inputs.set ?? {}).length === 0) {
448
481
  diags.push({
449
482
  level: 'error', code: 'DATAFABRIC_EMPTY_SET', step: s.name,
450
483
  message: `"${s.name}" writes no columns — { set } is empty, so the update would no-op green.`,
451
484
  });
452
485
  }
486
+ checkDataFabricColumns(s.name, Object.keys(s.spec.inputs.set ?? {}), 'set', diags);
453
487
  }
454
- else {
455
- dataFabricReads.add(s.name);
488
+ checkDataFabricScope(s.name, s.spec.inputs, diags);
489
+ if (s.spec.kind === 'dataFabricUpdate' || s.spec.kind === 'dataFabricDelete') {
490
+ const factory = s.spec.kind === 'dataFabricUpdate' ? 'dataFabricUpdate' : 'dataFabricDelete';
491
+ dataFabricRefs.push({ step: s.name, factory, record: s.spec.inputs.record });
492
+ if (s.spec.kind === 'dataFabricDelete')
493
+ dataFabricDeletes.add(s.name);
456
494
  }
457
495
  }
458
496
  if (s.spec.kind === 'ixpExtract')
@@ -729,12 +767,27 @@ function checkFlow(built, ancestors, opts = {}) {
729
767
  }
730
768
  }
731
769
  reportUnreachableHitlOutcomes(hitlChoiceNoPorts, exprs, diags);
732
- for (const { step, inputs } of dataFabricUpdates) {
733
- const fromRead = inputs.record?.fromRead;
734
- if (typeof fromRead === 'string' && !dataFabricReads.has(fromRead)) {
770
+ for (const { step, factory, record } of dataFabricRefs) {
771
+ const fromRead = record?.fromRead;
772
+ if (typeof fromRead !== 'string')
773
+ continue;
774
+ if (!dataFabricReads.has(fromRead)) {
735
775
  diags.push({
736
776
  level: 'error', code: 'DATAFABRIC_FROMREAD_UNKNOWN', step,
737
777
  message: `"${step}" targets record: { fromRead: ${JSON.stringify(fromRead)} }, but no dataFabricRead step has that name.`,
778
+ suggestion: `${factory}({ …, record: { byId: '<record id>' } })`,
779
+ });
780
+ continue;
781
+ }
782
+ // A LIST is not a write target: the serializer refuses it and emits a plain
783
+ // no-op task, so the flow validates, runs green, and writes nothing.
784
+ if (dataFabricMultiReads.has(fromRead)) {
785
+ diags.push({
786
+ level: 'error', code: 'DATAFABRIC_FROMREAD_MULTIPLE', step,
787
+ message: `"${step}" targets record: { fromRead: ${JSON.stringify(fromRead)} }, but "${fromRead}" is a `
788
+ + "resultMode: 'multiple' query. A multi-record read is refused as a write target — the step compiles to "
789
+ + 'a no-op and the run goes green having written nothing.',
790
+ suggestion: `loop the results and target each record with ${factory}({ …, record: { byId: … } })`,
738
791
  });
739
792
  }
740
793
  }
@@ -1002,6 +1055,20 @@ function checkFlow(built, ancestors, opts = {}) {
1002
1055
  });
1003
1056
  continue;
1004
1057
  }
1058
+ // A Data Fabric delete is the one verb of that family whose definition
1059
+ // declares no outputDefinition at all — the record is gone, so there is
1060
+ // nothing to publish. Read the record on the step that FOUND it instead,
1061
+ // which on the `{ fromRead }` form is already in the flow.
1062
+ if (dataFabricDeletes.has(name)) {
1063
+ diags.push({
1064
+ level: 'error',
1065
+ code: 'DATAFABRIC_DELETE_HAS_NO_OUTPUT',
1066
+ message: `Delete "${name}" produces no output, but "$vars.${name}${field ? '.' + field : ''}" reads one in "${step}". `
1067
+ + 'A Data Fabric delete declares no outputs — take the value off the dataFabricRead step that found the record, before the delete.',
1068
+ step,
1069
+ });
1070
+ continue;
1071
+ }
1005
1072
  if (delayNames.has(name)) {
1006
1073
  diags.push({
1007
1074
  level: 'error',
@@ -4635,6 +4702,102 @@ function checkCaseValueTypes(s, typeOf, diags, triggerId) {
4635
4702
  * `output` is the aggregate (after the loop) and `error` the failure detail.
4636
4703
  */
4637
4704
  const LOOP_FIELDS = new Set(['currentItem', 'currentIndex', 'collection', 'output', 'error']);
4705
+ /**
4706
+ * Columns Data Fabric assigns and never accepts a write for.
4707
+ *
4708
+ * The canvas hides them from both write editors, but the serializer has no
4709
+ * guard: a hand-authored `Id` row satisfies the node's own "at least one field"
4710
+ * rule, ships in the body, and is rejected — and the rejection is only logged,
4711
+ * so the row comes back unchanged and the run goes green.
4712
+ */
4713
+ const DATAFABRIC_SYSTEM_COLUMNS = new Set(['Id', 'CreateTime', 'CreatedBy', 'UpdateTime', 'UpdatedBy']);
4714
+ function checkDataFabricColumns(step, columns, option, diags) {
4715
+ const system = columns.filter((c) => DATAFABRIC_SYSTEM_COLUMNS.has(c));
4716
+ if (system.length) {
4717
+ diags.push({
4718
+ level: 'error', code: 'DATAFABRIC_SYSTEM_COLUMN', step,
4719
+ message: `"${step}" writes ${system.map((c) => JSON.stringify(c)).join(', ')} in { ${option} }. `
4720
+ + 'Data Fabric assigns its system columns (Id, CreateTime, CreatedBy, UpdateTime, UpdatedBy) and rejects a '
4721
+ + 'write to them — and it only LOGS the rejection, so the row comes back unchanged and the run goes green.',
4722
+ });
4723
+ }
4724
+ }
4725
+ /**
4726
+ * A folder-scoped entity is portable only with its `Entity` bindings.
4727
+ *
4728
+ * `_folderKey` on its own still serializes — it falls back to a source-org
4729
+ * literal and logs a warning — so the flow looks fine until it is deployed to
4730
+ * another org. Refusing the half-authored pair here is the whole point: the
4731
+ * alternative is a defect that only appears at deploy time, in a different org,
4732
+ * to somebody else.
4733
+ */
4734
+ function checkDataFabricScope(step, inputs, diags) {
4735
+ if (inputs.folderKey !== undefined && inputs.resourceKey === undefined) {
4736
+ diags.push({
4737
+ level: 'error', code: 'DATAFABRIC_FOLDER_NO_RESOURCE_KEY', step,
4738
+ message: `"${step}" sets { folderKey } without { resourceKey }. A folder-scoped entity needs both — with only `
4739
+ + 'the folder key the entity serializes as a source-org literal, which validates, runs in this org, and '
4740
+ + 'breaks on deploy to another one.',
4741
+ suggestion: 'resolve the key with `uip solution resources list --kind Entity`, or drop { folderKey } and keep '
4742
+ + 'the entity tenant-scoped',
4743
+ });
4744
+ }
4745
+ if (inputs.resourceKey !== undefined && inputs.folderKey === undefined) {
4746
+ diags.push({
4747
+ level: 'error', code: 'DATAFABRIC_RESOURCE_KEY_NO_FOLDER', step,
4748
+ message: `"${step}" sets { resourceKey } without { folderKey }. The pair exists to scope a FOLDER entity; a `
4749
+ + 'tenant-scoped entity needs neither and serializes as a dotted literal.',
4750
+ });
4751
+ }
4752
+ }
4753
+ /**
4754
+ * The query options, none of which the node's `inputDefinition` declares — they
4755
+ * are `_`-prefixed extras, so `uip maestro flow validate` checks none of them
4756
+ * and every mistake here is silent at run time.
4757
+ */
4758
+ function checkDataFabricQuery(step, inputs, diags) {
4759
+ const listOnly = ['limit', 'skip', 'sort', 'fields'].filter((k) => inputs[k] !== undefined);
4760
+ if (listOnly.length && inputs.resultMode !== 'multiple') {
4761
+ diags.push({
4762
+ level: 'error', code: 'DATAFABRIC_QUERY_OPTS_WITHOUT_MULTIPLE', step,
4763
+ message: `"${step}" sets { ${listOnly.join(', ')} }, which only a list query uses, but its resultMode is `
4764
+ + `${inputs.resultMode ? JSON.stringify(inputs.resultMode) : 'unset (a single-record read)'}.`,
4765
+ suggestion: "resultMode: 'multiple'",
4766
+ });
4767
+ }
4768
+ // The emitted query ALWAYS carries a limit — omitted it resolves to 100 — and
4769
+ // 1000 is a hard ceiling that TRUNCATES rather than erroring above it.
4770
+ if (inputs.limit !== undefined && (!Number.isInteger(inputs.limit) || inputs.limit < 1 || inputs.limit > 1000)) {
4771
+ diags.push({
4772
+ level: 'error', code: 'DATAFABRIC_LIMIT_RANGE', step,
4773
+ message: `"${step}": limit must be a whole number between 1 and 1000 (got ${inputs.limit}). 1000 is a hard `
4774
+ + 'ceiling — a larger value truncates silently, so page with { skip } instead.',
4775
+ });
4776
+ }
4777
+ // `in` is honoured only on a list query; on a single read the row compiles
4778
+ // away, and a read whose filters compiled away returns EVERY record.
4779
+ if (inputs.resultMode !== 'multiple' && (inputs.filters ?? []).some((f) => f.operator === 'in')) {
4780
+ diags.push({
4781
+ level: 'error', code: 'DATAFABRIC_IN_REQUIRES_MULTIPLE', step,
4782
+ message: `"${step}" filters with the 'in' operator, which is honoured only when resultMode is 'multiple'. `
4783
+ + 'On a single-record read that row compiles away — and a read whose filters compiled away matches every record.',
4784
+ });
4785
+ }
4786
+ // A filter expression switched on and left blank is refused by the serializer,
4787
+ // and it refuses the WHOLE query rather than dropping the row, which strands
4788
+ // every downstream reference to this step.
4789
+ for (const f of inputs.filters ?? []) {
4790
+ const blank = f.value instanceof Expr ? f.value.js.trim() === '' : String(f.value ?? '').trim() === '';
4791
+ if (blank) {
4792
+ diags.push({
4793
+ level: 'error', code: 'DATAFABRIC_FILTER_BLANK_VALUE', step,
4794
+ message: `"${step}" has a filter on ${JSON.stringify(f.field)} with a blank value. The serializer refuses the `
4795
+ + 'ENTIRE query rather than dropping the row, so the step emits nothing and every downstream $vars read of '
4796
+ + 'it breaks.',
4797
+ });
4798
+ }
4799
+ }
4800
+ }
4638
4801
  function collectActionExprs(s, out, errorSource) {
4639
4802
  const push = (val) => {
4640
4803
  if (val instanceof Expr && !val.literal)
@@ -4691,6 +4854,30 @@ function collectActionExprs(s, out, errorSource) {
4691
4854
  push(val);
4692
4855
  return;
4693
4856
  }
4857
+ if (s.spec.kind === 'dataFabricRead' || s.spec.kind === 'dataFabricCreate'
4858
+ || s.spec.kind === 'dataFabricUpdate' || s.spec.kind === 'dataFabricDelete') {
4859
+ // Every value a Data Fabric step carries is authored against flow scope: a
4860
+ // filter's comparison value, the record id, the columns written. The family
4861
+ // was missing here entirely, so a `$vars.` typo in any of them travelled to
4862
+ // the platform unchecked — and the delete verb's no-output rule below has
4863
+ // nothing to fire on until these are collected.
4864
+ if (s.spec.kind === 'dataFabricRead') {
4865
+ for (const f of s.spec.inputs.filters ?? [])
4866
+ push(f.value);
4867
+ return;
4868
+ }
4869
+ if (s.spec.kind === 'dataFabricCreate') {
4870
+ for (const val of Object.values(s.spec.inputs.values ?? {}))
4871
+ push(val);
4872
+ return;
4873
+ }
4874
+ push(s.spec.inputs.record?.byId);
4875
+ if (s.spec.kind === 'dataFabricUpdate') {
4876
+ for (const val of Object.values(s.spec.inputs.set ?? {}))
4877
+ push(val);
4878
+ }
4879
+ return;
4880
+ }
4694
4881
  if (s.spec.kind === 'connector') {
4695
4882
  // Same gap on the connector surface: `messageToSend: tmpl`…${v('typo')}``
4696
4883
  // reached the runtime unchecked.
@@ -2016,46 +2016,215 @@ export interface QueueItemInputs {
2016
2016
  */
2017
2017
  returns?: Record<string, ReturnFieldType>;
2018
2018
  }
2019
- /** One filter row on a Data Fabric read — rows AND together unless `or` is set. */
2019
+ /**
2020
+ * The comparison operators the Data Fabric query serializer can emit, spelled
2021
+ * exactly as the file carries them.
2022
+ *
2023
+ * @remarks
2024
+ * There is deliberately no `not in`, `not contains`, or null check — the
2025
+ * serializer cannot emit them, and a filter it cannot emit takes the WHOLE
2026
+ * query down rather than dropping one row. `is any of` is the legacy spelling
2027
+ * of `in` and is still read, but author `in`.
2028
+ */
2029
+ export type DataFabricOperator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'contains' | 'starts with' | 'ends with' | 'in';
2030
+ /** One filter row on a Data Fabric query — rows AND together unless `or` is set. */
2020
2031
  export interface DataFabricFilter {
2021
- /** The entity column to filter on. */
2032
+ /**
2033
+ * The entity column to filter on. A dotted related path (`customer.Name`) is
2034
+ * capped at 3 segments and needs the canvas's `_entityFields` snapshot, which
2035
+ * the SDK does not author — keep from-scratch filters on the entity's own
2036
+ * columns.
2037
+ */
2022
2038
  field: string;
2023
- /** Comparison operator the designer offers — `=` (the default), `!=`, `>`, `>=`, `<`, `<=`, `contains`, … */
2024
- operator?: string;
2025
- /** The comparison value. An `Expr` re-evaluates at activation time. */
2039
+ /** Comparison operator; `=` when omitted. */
2040
+ operator?: DataFabricOperator;
2041
+ /**
2042
+ * The comparison value. An `Expr` re-evaluates at activation time.
2043
+ *
2044
+ * A choice-set column compares against the choice's numeric `numberId`, never
2045
+ * its label — a label matches nothing.
2046
+ */
2026
2047
  value: string | Expr;
2027
- /** Join this row to the PREVIOUS one with OR instead of AND. */
2048
+ /**
2049
+ * Start a new OR alternative at this row.
2050
+ *
2051
+ * @remarks
2052
+ * The query model is disjunctive — `alt1 OR alt2 OR …`, each alternative an
2053
+ * AND of its own rows — so `or` SPLITS the list rather than joining one row
2054
+ * to the one before it. `[A, B(or), C]` is therefore `A OR (B AND C)`, not
2055
+ * `(A OR B) AND C`; to get the latter, write it out as
2056
+ * `(A AND C) OR (B AND C)`.
2057
+ *
2058
+ * Ignored on the first row, which has no earlier alternative to split from.
2059
+ */
2028
2060
  or?: boolean;
2029
2061
  }
2062
+ /** How a multi-record query orders its rows before the limit is applied. */
2063
+ export interface DataFabricSort {
2064
+ /** The column to order by — a real one; a sort on a missing column is not caught by `validate`. */
2065
+ field: string;
2066
+ /** `'asc'` when omitted. */
2067
+ direction?: 'asc' | 'desc';
2068
+ }
2069
+ /**
2070
+ * Which record a Data Fabric write targets — exactly ONE of the two forms.
2071
+ *
2072
+ * @remarks
2073
+ * `{ byId }` names a record id, literal or expression. `{ fromRead }` names an
2074
+ * earlier {@link dataFabricRead} STEP and reuses the record that step found, so
2075
+ * the id never has to be carried by hand; `check` rejects a name no read step
2076
+ * has. Shared by {@link dataFabricUpdate} and {@link dataFabricDelete} — the
2077
+ * platform gives them the same `recordSource` shape.
2078
+ */
2079
+ export type DataFabricRecordRef = {
2080
+ byId: string | Expr;
2081
+ fromRead?: never;
2082
+ } | {
2083
+ fromRead: string;
2084
+ byId?: never;
2085
+ };
2030
2086
  export interface DataFabricReadInputs {
2031
2087
  /** The Data Fabric entity's name, e.g. `'Invoices'`. */
2032
2088
  entity: string;
2033
- /** Filter rows selecting the record to read. Omit to read the first record. */
2089
+ /** Filter rows selecting the record(s) to query. Omit to take everything the entity holds. */
2034
2090
  filters?: DataFabricFilter[];
2035
- /** Folder key (GUID) for folder-scoped entities. */
2091
+ /**
2092
+ * `'single'` publishes ONE record as the step's `output`; `'multiple'`
2093
+ * publishes the matching records under `output.results`.
2094
+ *
2095
+ * Setting either selects the node's **1.4** definition, which is where
2096
+ * `resultMode` exists at all — the SDK's pinned default is 1.0, the
2097
+ * read-one-record node this family started as. Omit it and nothing changes:
2098
+ * the step stays on 1.0 and reads one record, as it always did.
2099
+ *
2100
+ * A `'single'` read faults only on an OVER-match; zero matches completes
2101
+ * normally and leaves the step's output unresolved, so treat "not found" as a
2102
+ * branch rather than an error.
2103
+ */
2104
+ resultMode?: 'single' | 'multiple';
2105
+ /**
2106
+ * Row cap for a `'multiple'` query. The emitted query ALWAYS carries a limit:
2107
+ * omitting this resolves to 100, and 1000 is a hard ceiling — a larger value
2108
+ * truncates silently, so page with `skip` instead.
2109
+ */
2110
+ limit?: number;
2111
+ /** Rows to skip before the limit — the paging half of `limit`. */
2112
+ skip?: number;
2113
+ /** Order rows before the limit applies. Without it a limited slice is arbitrary. */
2114
+ sort?: DataFabricSort;
2115
+ /** Only these columns. Omit for every column the entity has. */
2116
+ fields?: string[];
2117
+ /**
2118
+ * Folder key (GUID) for a folder-scoped entity — its `folderId` from
2119
+ * `uip df entities list --include-folders`.
2120
+ *
2121
+ * Requires `resourceKey`: `folderKey` alone serializes the folder as a
2122
+ * source-org LITERAL, which looks portable and breaks on cross-org deploy, so
2123
+ * `check` refuses the pair being half-authored.
2124
+ */
2036
2125
  folderKey?: string;
2126
+ /**
2127
+ * The key the entity's reference is registered under in the solution, from
2128
+ * `uip solution resources list --kind Entity`.
2129
+ *
2130
+ * @remarks
2131
+ * Only meaningful with `folderKey`. Supplying both emits the two
2132
+ * `resource: 'Entity'` binding rows (`propertyAttribute` `name` and
2133
+ * `folderKey`) that let the flow deploy to another org. Nothing mints this
2134
+ * key for a hand-authored flow — if you cannot resolve one, keep the entity
2135
+ * tenant-scoped and omit both.
2136
+ */
2137
+ resourceKey?: string;
2037
2138
  }
2038
- export interface DataFabricUpdateInputs {
2139
+ export interface DataFabricCreateInputs {
2039
2140
  /** The Data Fabric entity's name, e.g. `'Invoices'`. */
2040
2141
  entity: string;
2041
2142
  /**
2042
- * Which record to update: `{ byId: <record id> }`, or `{ fromRead: '<step>' }`
2043
- * naming an earlier `dataFabricRead` step whose record this write targets.
2143
+ * The columns the new record starts with, by field name. At least one is
2144
+ * required — the node's schema rejects an empty field list rather than
2145
+ * inserting a blank row.
2044
2146
  */
2045
- record: {
2046
- byId: string | Expr;
2047
- fromRead?: never;
2048
- } | {
2049
- fromRead: string;
2050
- byId?: never;
2051
- };
2147
+ values: Record<string, string | Expr>;
2148
+ /**
2149
+ * Folder key (GUID) for a folder-scoped entity — its `folderId` from
2150
+ * `uip df entities list --include-folders`.
2151
+ *
2152
+ * Requires `resourceKey`: `folderKey` alone serializes the folder as a
2153
+ * source-org LITERAL, which looks portable and breaks on cross-org deploy, so
2154
+ * `check` refuses the pair being half-authored.
2155
+ */
2156
+ folderKey?: string;
2157
+ /**
2158
+ * The key the entity's reference is registered under in the solution, from
2159
+ * `uip solution resources list --kind Entity`.
2160
+ *
2161
+ * @remarks
2162
+ * Only meaningful with `folderKey`. Supplying both emits the two
2163
+ * `resource: 'Entity'` binding rows (`propertyAttribute` `name` and
2164
+ * `folderKey`) that let the flow deploy to another org. Nothing mints this
2165
+ * key for a hand-authored flow — if you cannot resolve one, keep the entity
2166
+ * tenant-scoped and omit both.
2167
+ */
2168
+ resourceKey?: string;
2169
+ }
2170
+ export interface DataFabricUpdateInputs {
2171
+ /** The Data Fabric entity's name, e.g. `'Invoices'`. */
2172
+ entity: string;
2173
+ /** Which record to update — see {@link DataFabricRecordRef}. */
2174
+ record: DataFabricRecordRef;
2052
2175
  /**
2053
2176
  * The columns to write, by field name. An empty string is a real write — it
2054
2177
  * clears the column.
2055
2178
  */
2056
2179
  set: Record<string, string | Expr>;
2057
- /** Folder key (GUID) for folder-scoped entities. */
2180
+ /**
2181
+ * Folder key (GUID) for a folder-scoped entity — its `folderId` from
2182
+ * `uip df entities list --include-folders`.
2183
+ *
2184
+ * Requires `resourceKey`: `folderKey` alone serializes the folder as a
2185
+ * source-org LITERAL, which looks portable and breaks on cross-org deploy, so
2186
+ * `check` refuses the pair being half-authored.
2187
+ */
2188
+ folderKey?: string;
2189
+ /**
2190
+ * The key the entity's reference is registered under in the solution, from
2191
+ * `uip solution resources list --kind Entity`.
2192
+ *
2193
+ * @remarks
2194
+ * Only meaningful with `folderKey`. Supplying both emits the two
2195
+ * `resource: 'Entity'` binding rows (`propertyAttribute` `name` and
2196
+ * `folderKey`) that let the flow deploy to another org. Nothing mints this
2197
+ * key for a hand-authored flow — if you cannot resolve one, keep the entity
2198
+ * tenant-scoped and omit both.
2199
+ */
2200
+ resourceKey?: string;
2201
+ }
2202
+ export interface DataFabricDeleteInputs {
2203
+ /** The Data Fabric entity's name, e.g. `'Invoices'`. */
2204
+ entity: string;
2205
+ /** Which record to delete — see {@link DataFabricRecordRef}. */
2206
+ record: DataFabricRecordRef;
2207
+ /**
2208
+ * Folder key (GUID) for a folder-scoped entity — its `folderId` from
2209
+ * `uip df entities list --include-folders`.
2210
+ *
2211
+ * Requires `resourceKey`: `folderKey` alone serializes the folder as a
2212
+ * source-org LITERAL, which looks portable and breaks on cross-org deploy, so
2213
+ * `check` refuses the pair being half-authored.
2214
+ */
2058
2215
  folderKey?: string;
2216
+ /**
2217
+ * The key the entity's reference is registered under in the solution, from
2218
+ * `uip solution resources list --kind Entity`.
2219
+ *
2220
+ * @remarks
2221
+ * Only meaningful with `folderKey`. Supplying both emits the two
2222
+ * `resource: 'Entity'` binding rows (`propertyAttribute` `name` and
2223
+ * `folderKey`) that let the flow deploy to another org. Nothing mints this
2224
+ * key for a hand-authored flow — if you cannot resolve one, keep the entity
2225
+ * tenant-scoped and omit both.
2226
+ */
2227
+ resourceKey?: string;
2059
2228
  }
2060
2229
  export interface DocumentClassifyInputs {
2061
2230
  /** The document to classify — a file reference (`input('file')`, `out('fetch', 'fileRef')`, …). */
@@ -2191,9 +2360,15 @@ export type ActionSpec = {
2191
2360
  } | {
2192
2361
  kind: 'dataFabricRead';
2193
2362
  inputs: DataFabricReadInputs;
2363
+ } | {
2364
+ kind: 'dataFabricCreate';
2365
+ inputs: DataFabricCreateInputs;
2194
2366
  } | {
2195
2367
  kind: 'dataFabricUpdate';
2196
2368
  inputs: DataFabricUpdateInputs;
2369
+ } | {
2370
+ kind: 'dataFabricDelete';
2371
+ inputs: DataFabricDeleteInputs;
2197
2372
  } | {
2198
2373
  kind: 'waitEvent';
2199
2374
  subscription: EventSubscription;
@@ -2929,63 +3104,97 @@ export declare function documentClassify(inputs: DocumentClassifyInputs): Action
2929
3104
  */
2930
3105
  export declare function dynamicExtract(inputs: DynamicExtractInputs): ActionSpec;
2931
3106
  /**
2932
- * Read ONE entity record from Data Fabric (`core.datafabric.read`). The record
3107
+ * Query entity records from Data Fabric (`core.datafabric.read`). The result
2933
3108
  * publishes to the step's `output`; downstream references re-fetch fresh data
2934
3109
  * at activation time (the engine re-runs the query rather than caching).
2935
3110
  *
2936
3111
  * @remarks
2937
- * **Prefer the connector for Data Fabric work during the transition.**
2938
- * `core.datafabric.*` has exactly two verbs — this one and
2939
- * {@link dataFabricUpdate} — and neither declares an output schema. Create,
2940
- * delete, get-by-id, a multi-record query with a row limit, file record fields
2941
- * and entity events are NOT in this family: they are operations on the SAME
2942
- * product reached through its Integration Service connector,
3112
+ * This node is **read-one AND query-many**, on its 1.4 definition: `resultMode:
3113
+ * 'multiple'` publishes the matching records under `output.results`, and the
3114
+ * platform renames the node "Query entity records" to say so. The SDK still
3115
+ * pins 1.0 — read-one — as the no-`{ version }` default, so passing
3116
+ * `resultMode` is what selects 1.4; see {@link DataFabricReadInputs.resultMode}.
3117
+ *
3118
+ * The native family covers all four CRUD verbs: this one,
3119
+ * {@link dataFabricCreate}, {@link dataFabricUpdate} and
3120
+ * {@link dataFabricDelete}. What it does NOT cover is get-by-id, a query row
3121
+ * limit, file record fields, and entity events — those are operations on the
3122
+ * SAME product reached through its Integration Service connector,
2943
3123
  * `uipath-uipath-dataservice` (the tenant displays it as **UiPath Data
2944
- * Fabric**), which covers every verb including the two here.
2945
- *
2946
- * Taking this node for the one verb that has a native form leaves the rest of
2947
- * the same entity on the connector — two connection bindings and two payload
2948
- * shapes in one flow. So route entity work through the connector unless the
2949
- * scenario names these nodes; native forms for the remaining verbs are the
2950
- * direction of travel, and this guidance changes when they land. `check` does
2951
- * not reject a native node — this is a routing default, not a rule:
3124
+ * Fabric**). So a flow that needs one of those still straddles two surfaces,
3125
+ * and the connector remains the one surface that covers everything:
2952
3126
  *
2953
3127
  * ```ts
2954
- * .step('create', connector('uipath-uipath-dataservice', 'create-entity-record',
2955
- * { entityName: 'Contracts', contractTitle: 'Q3 renewal' }, { connection: 'df' }))
3128
+ * .step('byId', connector('uipath-uipath-dataservice', 'get-entity-record-by-id',
3129
+ * { entityName: 'Contracts', recordId: input('id') }, { connection: 'df' }))
2956
3130
  * ```
2957
3131
  *
2958
- * That operation's body fields come from the entity, so the library cannot
2959
- * carry them: run `npx flow-sdk registry prepare uipath-uipath-dataservice
2960
- * create-entity-record -f entityName=<Entity>` first (`uip maestro registry
2961
- * prepare …` is the same command when you are driving through the UiPath CLI). The routing table is in
2962
- * the `data-fabric.md` reference and the parent-field loop in
2963
- * `connector-params.md` — cited by name rather than linked, because this
3132
+ * A connector operation's body fields come from the entity, so the library
3133
+ * cannot carry them: run `npx flow-sdk registry prepare uipath-uipath-dataservice
3134
+ * get-entity-record-by-id -f entityName=<Entity>` first (`uip maestro registry
3135
+ * prepare …` is the same command when you are driving through the UiPath CLI).
3136
+ * The routing table is in the `data-fabric.md` reference and the parent-field
3137
+ * loop in `connector-params.md` — cited by name rather than linked, because this
2964
3138
  * comment renders both into the flat corpus, where those files sit alongside
2965
3139
  * it, and onto the site, where each symbol is its own page and a relative
2966
3140
  * `./data-fabric` resolves to nothing.
2967
3141
  *
2968
- * @param inputs - The entity, optional filter rows, and folder scope.
3142
+ * @param inputs - The entity, optional filter rows, result mode, and folder scope.
2969
3143
  * @returns An action spec to pass to `.step(name, …)`.
2970
3144
  */
2971
3145
  export declare function dataFabricRead(inputs: DataFabricReadInputs): ActionSpec;
3146
+ /**
3147
+ * Create an entity record in Data Fabric (`core.datafabric.create`). The new
3148
+ * record publishes to the step's `output`, so a following
3149
+ * {@link dataFabricUpdate} or {@link dataFabricDelete} can read its id.
3150
+ *
3151
+ * @remarks
3152
+ * `values` must name at least one column — the node's schema rejects an empty
3153
+ * field list rather than inserting a blank row, and `check` says so before the
3154
+ * platform does. Field names are the ENTITY's, so copy them from the scenario
3155
+ * or the tenant; nothing offline can tell a real column from a typo.
3156
+ *
3157
+ * See {@link dataFabricRead} for what the native family still does not cover
3158
+ * and which connector operation covers it.
3159
+ *
3160
+ * @param inputs - The entity, the columns the record starts with, and folder scope.
3161
+ * @returns An action spec to pass to `.step(name, …)`.
3162
+ */
3163
+ export declare function dataFabricCreate(inputs: DataFabricCreateInputs): ActionSpec;
2972
3164
  /**
2973
3165
  * Update an entity record in Data Fabric (`core.datafabric.update`). Target the
2974
3166
  * record by id or through an earlier {@link dataFabricRead} step; downstream
2975
3167
  * reads of this step observe the record AFTER the write.
2976
3168
  *
2977
3169
  * @remarks
2978
- * The second and last verb of the native family — see {@link dataFabricRead}
2979
- * for what the family does NOT cover, which connector operation covers it
2980
- * instead, and why entity work routes through the connector during the
2981
- * transition. For an update whose flow also creates, gets or deletes on the
2982
- * same entity, use `connector(…, 'update-entity-record', …)` so the flow keeps
2983
- * one surface.
3170
+ * `set` must write at least one column: an empty `set` is a no-op the platform
3171
+ * reports as success, so `check` rejects it.
3172
+ *
3173
+ * See {@link dataFabricRead} for what the native family still does not cover
3174
+ * and which connector operation covers it.
2984
3175
  *
2985
3176
  * @param inputs - The entity, the record selector, and the columns to write.
2986
3177
  * @returns An action spec to pass to `.step(name, …)`.
2987
3178
  */
2988
3179
  export declare function dataFabricUpdate(inputs: DataFabricUpdateInputs): ActionSpec;
3180
+ /**
3181
+ * Delete an entity record from Data Fabric (`core.datafabric.delete`). Target
3182
+ * the record by id or through an earlier {@link dataFabricRead} step, exactly
3183
+ * as {@link dataFabricUpdate} does.
3184
+ *
3185
+ * @remarks
3186
+ * A delete **publishes nothing** — its definition declares no output — so
3187
+ * `$vars.<step>.output` is undefined at run time and `check` rejects a read of
3188
+ * it. Take what you need off the read step that found the record, before the
3189
+ * delete.
3190
+ *
3191
+ * See {@link dataFabricRead} for what the native family still does not cover
3192
+ * and which connector operation covers it.
3193
+ *
3194
+ * @param inputs - The entity, the record selector, and folder scope.
3195
+ * @returns An action spec to pass to `.step(name, …)`.
3196
+ */
3197
+ export declare function dataFabricDelete(inputs: DataFabricDeleteInputs): ActionSpec;
2989
3198
  /**
2990
3199
  * Declare an Orchestrator QUEUE ITEM step.
2991
3200
  *