@octaviaflow/flow-rules 0.17.0 → 0.19.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/index.js CHANGED
@@ -1022,32 +1022,51 @@ var QueryType;
1022
1022
  QueryType["Delete"] = "delete";
1023
1023
  QueryType["Custom"] = "custom";
1024
1024
  })(QueryType ||= {});
1025
- var LogicalOperator;
1026
- ((LogicalOperator) => {
1027
- LogicalOperator["And"] = "and";
1028
- LogicalOperator["Or"] = "or";
1029
- })(LogicalOperator ||= {});
1025
+ var QUERY_MODES = ["visual" /* Visual */];
1026
+ var QUERY_TYPES = ["select" /* Select */];
1027
+ var QUERY_COMPARISON_OPERATORS = [
1028
+ "=",
1029
+ "!=",
1030
+ ">",
1031
+ "<",
1032
+ ">=",
1033
+ "<=",
1034
+ "equals",
1035
+ "notEquals",
1036
+ "greaterThan",
1037
+ "lessThan",
1038
+ "greaterOrEqual",
1039
+ "lessOrEqual"
1040
+ ];
1041
+ var QUERY_RELATIVE_DATE_OPERATORS = ["inRelative", "notInRelative"];
1042
+ var QUERY_CONDITION_OPERATORS = [
1043
+ ...QUERY_COMPARISON_OPERATORS,
1044
+ "contains",
1045
+ "startsWith",
1046
+ "endsWith",
1047
+ "like",
1048
+ "notLike",
1049
+ "in",
1050
+ "notIn",
1051
+ "between",
1052
+ "isNull",
1053
+ "isNotNull",
1054
+ ...QUERY_RELATIVE_DATE_OPERATORS
1055
+ ];
1056
+ var RELATIVE_DATE_DIRECTIONS = ["last", "next", "this"];
1057
+ var RELATIVE_DATE_UNITS = ["day", "week", "month", "quarter", "year"];
1058
+ var RELATIVE_DATE_MAX_AMOUNT = 3650;
1059
+ var MAX_CONDITION_DEPTH = 3;
1060
+ var isConditionGroup = (c) => !!c && typeof c === "object" && c.type === "group";
1061
+ var QUERY_AGGREGATE_FUNCTIONS = ["COUNT", "COUNT_DISTINCT", "SUM", "AVG", "MIN", "MAX"];
1030
1062
  var SortDirection;
1031
1063
  ((SortDirection) => {
1032
1064
  SortDirection["Ascending"] = "asc";
1033
1065
  SortDirection["Descending"] = "desc";
1034
1066
  })(SortDirection ||= {});
1035
- var JoinType;
1036
- ((JoinType) => {
1037
- JoinType["Inner"] = "inner";
1038
- JoinType["Left"] = "left";
1039
- JoinType["Right"] = "right";
1040
- JoinType["Full"] = "full";
1041
- })(JoinType ||= {});
1042
- var AggregateFunction;
1043
- ((AggregateFunction) => {
1044
- AggregateFunction["Count"] = "count";
1045
- AggregateFunction["Sum"] = "sum";
1046
- AggregateFunction["Average"] = "avg";
1047
- AggregateFunction["Min"] = "min";
1048
- AggregateFunction["Max"] = "max";
1049
- AggregateFunction["CountDistinct"] = "count_distinct";
1050
- })(AggregateFunction ||= {});
1067
+ // src/catalog/isOneOf.ts
1068
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
1069
+
1051
1070
  // src/catalog/general/query/config.ts
1052
1071
  var BuildQueryConfigDefaults = {
1053
1072
  mode: "visual" /* Visual */,
@@ -1057,160 +1076,262 @@ var BuildQueryConfigDefaults = {
1057
1076
  visualConfig: {
1058
1077
  tables: [],
1059
1078
  fields: [],
1060
- filters: undefined,
1079
+ conditions: [],
1061
1080
  joins: [],
1062
1081
  groupBy: [],
1082
+ aggregates: [],
1063
1083
  sort: [],
1064
- limit: 10,
1065
- offset: 0
1066
- },
1067
- sqlConfig: {
1068
- sql: "",
1069
- parameters: {}
1084
+ limit: 10
1070
1085
  }
1071
1086
  };
1072
- var validateBuildQueryConfig = (config) => {
1073
- const errors = [];
1074
- const warnings = [];
1075
- if (!config.connectorId) {
1076
- errors.push({
1077
- field: "connectorId",
1078
- message: "Please select a connector",
1079
- severity: "error"
1080
- });
1087
+ var QUERY_VISUAL_ONLY_MESSAGE = "The Query step is built with the visual builder only — set mode to 'visual' and describe the query in " + "visualConfig (tables, fields, conditions, sort, groupBy, aggregates). Hand-written SQL is not accepted.";
1088
+ var QUERY_AUTHORED_SQL_MESSAGE = "sqlConfig.sql is written by the visual builder, not by hand — remove sqlConfig and describe the query in " + "visualConfig (tables, fields, conditions, sort, groupBy, aggregates) instead.";
1089
+ var err = (field, message) => ({ field, message, severity: "error" });
1090
+ var isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1091
+ var nonEmptyString = (v) => typeof v === "string" && v.trim().length > 0;
1092
+ var hasValue = (v) => v !== undefined && v !== null && !(typeof v === "string" && v.trim() === "") && !(Array.isArray(v) && v.length === 0);
1093
+ var arr = (v) => Array.isArray(v) ? v : [];
1094
+ function queryFieldsSatisfied(config) {
1095
+ const vc = isRecord(config?.visualConfig) ? config.visualConfig : undefined;
1096
+ if (!vc)
1097
+ return false;
1098
+ if (vc.selectAll === true)
1099
+ return true;
1100
+ if (arr(vc.fields).length > 0)
1101
+ return true;
1102
+ if (arr(vc.aggregates).length > 0 || arr(vc.groupBy).length > 0)
1103
+ return true;
1104
+ const sql = isRecord(config?.sqlConfig) ? config.sqlConfig.sql : undefined;
1105
+ return arr(vc.tables).length > 0 && nonEmptyString(sql);
1106
+ }
1107
+ function queryTablesSatisfied(config) {
1108
+ const vc = isRecord(config?.visualConfig) ? config.visualConfig : undefined;
1109
+ if (!vc)
1110
+ return false;
1111
+ return vc.selectAll === true || arr(vc.tables).length > 0;
1112
+ }
1113
+ var OPERATOR_LIST = QUERY_CONDITION_OPERATORS.join(", ");
1114
+ function validateRelative(path, relative) {
1115
+ if (!isRecord(relative)) {
1116
+ return [err(`${path}.relative`, `${path}: a relative-date condition needs relative, e.g. { direction: 'last', amount: 30, unit: 'day' }`)];
1081
1117
  }
1082
- if (!config.mode) {
1083
- errors.push({
1084
- field: "mode",
1085
- message: "Query mode is required",
1086
- severity: "error"
1087
- });
1118
+ const issues = [];
1119
+ const { direction, unit, amount, includeCurrent } = relative;
1120
+ if (!isOneOf(RELATIVE_DATE_DIRECTIONS, direction)) {
1121
+ issues.push(err(`${path}.relative.direction`, `${path}.relative.direction must be one of: ${RELATIVE_DATE_DIRECTIONS.join(", ")} (got "${String(direction)}")`));
1088
1122
  }
1089
- if (!config.queryType) {
1090
- errors.push({
1091
- field: "queryType",
1092
- message: "Query type is required",
1093
- severity: "error"
1094
- });
1123
+ if (!isOneOf(RELATIVE_DATE_UNITS, unit)) {
1124
+ issues.push(err(`${path}.relative.unit`, `${path}.relative.unit must be one of: ${RELATIVE_DATE_UNITS.join(", ")} (got "${String(unit)}")`));
1095
1125
  }
1096
- if (config.mode === "visual" /* Visual */) {
1097
- const visualErrors = validateVisualQueryConfig(config.visualConfig);
1098
- errors.push(...visualErrors);
1099
- } else if (config.mode === "sql" /* SQL */) {
1100
- const sqlErrors = validateSQLQueryConfig(config.sqlConfig);
1101
- errors.push(...sqlErrors);
1126
+ if (direction === "last" || direction === "next") {
1127
+ const ok = typeof amount === "number" && Number.isInteger(amount) && amount >= 1 && amount <= RELATIVE_DATE_MAX_AMOUNT;
1128
+ if (!ok) {
1129
+ issues.push(err(`${path}.relative.amount`, `${path}.relative.amount must be a whole number from 1 to ${RELATIVE_DATE_MAX_AMOUNT} for '${direction}' ` + `(e.g. last 30 days is { direction: 'last', amount: 30, unit: 'day' })`));
1130
+ }
1102
1131
  }
1103
- if (config.timeout !== undefined) {
1104
- if (config.timeout < 1000) {
1105
- errors.push({
1106
- field: "timeout",
1107
- message: "Timeout must be at least 1000ms",
1108
- severity: "error"
1109
- });
1110
- } else if (config.timeout > 300000) {
1111
- warnings.push({
1112
- field: "timeout",
1113
- message: "Timeout is very high (>5 minutes)",
1114
- severity: "warning"
1115
- });
1132
+ if (includeCurrent !== undefined && typeof includeCurrent !== "boolean") {
1133
+ issues.push(err(`${path}.relative.includeCurrent`, `${path}.relative.includeCurrent must be true or false`));
1134
+ }
1135
+ return issues;
1136
+ }
1137
+ function validateRow(path, row, inHaving) {
1138
+ const issues = [];
1139
+ if (!nonEmptyString(row.field)) {
1140
+ issues.push(err(`${path}.field`, `${path}: choose the field to compare (field is empty)`));
1141
+ }
1142
+ if (!isOneOf(QUERY_CONDITION_OPERATORS, row.operator)) {
1143
+ issues.push(err(`${path}.operator`, `${path}.operator must be one of: ${OPERATOR_LIST} (got "${String(row.operator)}")`));
1144
+ return issues;
1145
+ }
1146
+ const op = row.operator;
1147
+ const isRelative = isOneOf(QUERY_RELATIVE_DATE_OPERATORS, op);
1148
+ const comparesField = row.valueField !== undefined && row.valueField !== null && row.valueField !== "";
1149
+ if (isRelative) {
1150
+ if (inHaving) {
1151
+ issues.push(err(`${path}.operator`, `${path}: '${op}' filters dates on rows — put it in conditions, not having`));
1152
+ } else {
1153
+ issues.push(...validateRelative(path, row.relative));
1154
+ }
1155
+ if (hasValue(row.value) || comparesField) {
1156
+ issues.push(err(path, `${path}: a relative-date condition takes its window from relative — remove value and valueField`));
1116
1157
  }
1158
+ } else if (row.relative !== undefined) {
1159
+ issues.push(err(`${path}.relative`, `${path}: relative is only used with 'inRelative' or 'notInRelative' — change the operator or remove relative`));
1117
1160
  }
1118
- if (config.maxRows !== undefined) {
1119
- if (config.maxRows < 1) {
1120
- errors.push({
1121
- field: "maxRows",
1122
- message: "Max rows must be at least 1",
1123
- severity: "error"
1124
- });
1125
- } else if (config.maxRows > 1e4) {
1126
- warnings.push({
1127
- field: "maxRows",
1128
- message: "Max rows is very high (>10,000). This may impact performance.",
1129
- severity: "warning"
1130
- });
1161
+ if (comparesField) {
1162
+ if (inHaving) {
1163
+ issues.push(err(`${path}.valueField`, `${path}: having compares with a value — use value, not valueField`));
1164
+ } else if (!nonEmptyString(row.valueField)) {
1165
+ issues.push(err(`${path}.valueField`, `${path}.valueField must name the field to compare with`));
1166
+ } else if (!isOneOf(QUERY_COMPARISON_OPERATORS, op)) {
1167
+ issues.push(err(`${path}.valueField`, `${path}: comparing with another field works with =, !=, >, <, >=, <= only (got "${op}")`));
1168
+ }
1169
+ if (hasValue(row.value)) {
1170
+ issues.push(err(path, `${path}: set either value (compare with a value) or valueField (compare with another field), not both`));
1131
1171
  }
1132
1172
  }
1133
- return {
1134
- valid: errors.length === 0,
1135
- errors,
1136
- warnings
1137
- };
1138
- };
1139
- var validateVisualQueryConfig = (config) => {
1173
+ if (row.joinOperator !== undefined && !(typeof row.joinOperator === "string" && /^(and|or)$/i.test(row.joinOperator))) {
1174
+ issues.push(err(`${path}.joinOperator`, `${path}.joinOperator must be AND or OR (got "${String(row.joinOperator)}")`));
1175
+ }
1176
+ return issues;
1177
+ }
1178
+ function validateConditionList(path, list, depth) {
1179
+ if (list === undefined || list === null)
1180
+ return [];
1181
+ if (!Array.isArray(list))
1182
+ return [err(path, `${path} must be a list of conditions`)];
1183
+ const issues = [];
1184
+ list.forEach((item, i) => {
1185
+ const at = `${path}[${i}]`;
1186
+ if (!isRecord(item)) {
1187
+ issues.push(err(at, `${at} must be a condition ({ field, operator, value }) or a group ({ type: 'group', conditions })`));
1188
+ return;
1189
+ }
1190
+ if (isConditionGroup(item)) {
1191
+ if (depth + 1 > MAX_CONDITION_DEPTH) {
1192
+ issues.push(err(at, `${at}: groups can nest at most ${MAX_CONDITION_DEPTH} levels deep — move this group up a level or combine its conditions`));
1193
+ return;
1194
+ }
1195
+ if (item.joinOperator !== undefined && !(typeof item.joinOperator === "string" && /^(and|or)$/i.test(item.joinOperator))) {
1196
+ issues.push(err(`${at}.joinOperator`, `${at}.joinOperator must be AND or OR (got "${String(item.joinOperator)}")`));
1197
+ }
1198
+ if (!Array.isArray(item.conditions) || item.conditions.length === 0) {
1199
+ issues.push(err(`${at}.conditions`, `${at}: a group needs at least one condition — add one or remove the group`));
1200
+ return;
1201
+ }
1202
+ issues.push(...validateConditionList(`${at}.conditions`, item.conditions, depth + 1));
1203
+ return;
1204
+ }
1205
+ issues.push(...validateRow(at, item, false));
1206
+ });
1207
+ return issues;
1208
+ }
1209
+ function validateAggregates(vc) {
1210
+ const list = vc.aggregates;
1211
+ if (list === undefined || list === null)
1212
+ return [];
1213
+ if (!Array.isArray(list))
1214
+ return [err("visualConfig.aggregates", "visualConfig.aggregates must be a list of { fn, field, alias }")];
1215
+ const issues = [];
1216
+ list.forEach((a, i) => {
1217
+ const at = `visualConfig.aggregates[${i}]`;
1218
+ if (!isRecord(a)) {
1219
+ issues.push(err(at, `${at} must be { fn, field, alias }`));
1220
+ return;
1221
+ }
1222
+ const fn = typeof a.fn === "string" ? a.fn.toUpperCase() : a.fn;
1223
+ if (!isOneOf(QUERY_AGGREGATE_FUNCTIONS, fn)) {
1224
+ issues.push(err(`${at}.fn`, `${at}.fn must be one of: ${QUERY_AGGREGATE_FUNCTIONS.join(", ")} (got "${String(a.fn)}")`));
1225
+ return;
1226
+ }
1227
+ const allRows = !nonEmptyString(a.field) || a.field.trim() === "*";
1228
+ if (allRows && fn !== "COUNT") {
1229
+ issues.push(err(`${at}.field`, `${at}: ${fn} needs a field to work on — set field to a column name`));
1230
+ }
1231
+ if (a.alias !== undefined && a.alias !== null && !nonEmptyString(a.alias)) {
1232
+ issues.push(err(`${at}.alias`, `${at}.alias must be a name for the result column, or left out`));
1233
+ }
1234
+ });
1235
+ return issues;
1236
+ }
1237
+ function validateHaving(vc) {
1238
+ const list = vc.having;
1239
+ if (list === undefined || list === null || Array.isArray(list) && list.length === 0)
1240
+ return [];
1241
+ if (!Array.isArray(list))
1242
+ return [err("visualConfig.having", "visualConfig.having must be a list of conditions")];
1243
+ const aggregates = arr(vc.aggregates).filter(isRecord);
1244
+ const groupBy = arr(vc.groupBy).filter(nonEmptyString);
1245
+ if (aggregates.length === 0 && groupBy.length === 0) {
1246
+ return [err("visualConfig.having", "visualConfig.having filters grouped results, so it needs aggregates or groupBy — add them, or move these filters to conditions")];
1247
+ }
1248
+ const names = new Set([
1249
+ ...groupBy,
1250
+ ...aggregates.map((a) => a.alias).filter(nonEmptyString)
1251
+ ]);
1252
+ const issues = [];
1253
+ list.forEach((row, i) => {
1254
+ const at = `visualConfig.having[${i}]`;
1255
+ if (!isRecord(row) || isConditionGroup(row)) {
1256
+ issues.push(err(at, `${at} must be a single condition ({ field, operator, value }); groups are not used in having`));
1257
+ return;
1258
+ }
1259
+ issues.push(...validateRow(at, row, true));
1260
+ if (nonEmptyString(row.field) && !names.has(row.field)) {
1261
+ const known = [...names];
1262
+ issues.push(err(`${at}.field`, `${at}.field must be an aggregate alias or a groupBy column (got "${row.field}"` + (known.length > 0 ? `; available: ${known.join(", ")})` : "; give the aggregate an alias first)")));
1263
+ }
1264
+ });
1265
+ return issues;
1266
+ }
1267
+ function validateVisualConfig(config) {
1268
+ const vc = config.visualConfig;
1269
+ if (!isRecord(vc)) {
1270
+ return [err("visualConfig", "visualConfig is required — describe the query with tables (what to read), fields (which columns) and optional conditions")];
1271
+ }
1272
+ const issues = [];
1273
+ if (!queryTablesSatisfied(config)) {
1274
+ issues.push(err("visualConfig.tables", "At least one table must be selected (visualConfig.tables)"));
1275
+ }
1276
+ if (!queryFieldsSatisfied(config)) {
1277
+ issues.push(err("visualConfig.fields", "At least one field must be selected (visualConfig.fields), or add aggregates / groupBy for a summary query"));
1278
+ }
1279
+ if (arr(vc.tables).length > 1 && arr(vc.joins).length === 0) {
1280
+ issues.push(err("visualConfig.joins", "Joins are required when selecting from multiple tables (visualConfig.joins)"));
1281
+ }
1282
+ if (vc.distinct !== undefined && typeof vc.distinct !== "boolean") {
1283
+ issues.push(err("visualConfig.distinct", "visualConfig.distinct must be true or false"));
1284
+ }
1285
+ issues.push(...validateConditionList("visualConfig.conditions", vc.conditions, 0));
1286
+ issues.push(...validateAggregates(vc));
1287
+ issues.push(...validateHaving(vc));
1288
+ return issues;
1289
+ }
1290
+ var validateBuildQueryConfig = (config) => {
1291
+ const c = config;
1140
1292
  const errors = [];
1141
- if (!config) {
1142
- errors.push({
1143
- field: "visualConfig",
1144
- message: "Visual query configuration is required",
1145
- severity: "error"
1146
- });
1147
- return errors;
1293
+ const warnings = [];
1294
+ if (!c.connectorId) {
1295
+ errors.push(err("connectorId", "Please select a connector (connectorId)"));
1148
1296
  }
1149
- if (!config.tables || config.tables.length === 0) {
1150
- errors.push({
1151
- field: "visualConfig.tables",
1152
- message: "At least one table must be selected",
1153
- severity: "error"
1154
- });
1297
+ const mode = c.mode;
1298
+ const visual = mode === undefined || mode === null || mode === "" || isOneOf(QUERY_MODES, mode);
1299
+ if (!visual) {
1300
+ errors.push(err("mode", `Mode must be one of: ${QUERY_MODES.join(", ")} (got "${String(mode)}"). ${QUERY_VISUAL_ONLY_MESSAGE}`));
1301
+ } else if (!mode) {
1302
+ errors.push(err("mode", "Query mode is required — set mode to 'visual'"));
1155
1303
  }
1156
- if (!config.fields || config.fields.length === 0) {
1157
- errors.push({
1158
- field: "visualConfig.fields",
1159
- message: "At least one field must be selected",
1160
- severity: "error"
1161
- });
1304
+ const queryType = c.queryType;
1305
+ if (!queryType) {
1306
+ errors.push(err("queryType", "Query type is required — set queryType to 'select'"));
1307
+ } else if (!isOneOf(QUERY_TYPES, queryType)) {
1308
+ errors.push(err("queryType", `Query type must be one of: ${QUERY_TYPES.join(", ")} (got "${String(queryType)}"). ` + "The Query step only reads data — use a Transform step to insert, update or delete."));
1162
1309
  }
1163
- if (config.tables && config.tables.length > 1) {
1164
- if (!config.joins || config.joins.length === 0) {
1165
- errors.push({
1166
- field: "visualConfig.joins",
1167
- message: "Joins are required when selecting from multiple tables",
1168
- severity: "error"
1169
- });
1310
+ if (visual) {
1311
+ const sql = isRecord(c.sqlConfig) ? c.sqlConfig.sql : undefined;
1312
+ if (nonEmptyString(sql) && !isRecord(c.visualConfig)) {
1313
+ errors.push(err("sqlConfig.sql", QUERY_AUTHORED_SQL_MESSAGE));
1314
+ } else {
1315
+ errors.push(...validateVisualConfig(c));
1170
1316
  }
1171
1317
  }
1172
- const hasAggregates = config.fields?.some((field) => field.aggregate);
1173
- const hasNonAggregates = config.fields?.some((field) => !field.aggregate);
1174
- if (hasAggregates && hasNonAggregates) {
1175
- if (!config.groupBy || config.groupBy.length === 0) {
1176
- errors.push({
1177
- field: "visualConfig.groupBy",
1178
- message: "Group by is required when mixing aggregate and non-aggregate fields",
1179
- severity: "error"
1180
- });
1318
+ const timeout = c.timeout;
1319
+ if (typeof timeout === "number") {
1320
+ if (timeout < 1000) {
1321
+ errors.push(err("timeout", "Timeout must be at least 1000ms"));
1322
+ } else if (timeout > 300000) {
1323
+ warnings.push({ field: "timeout", message: "Timeout is very high (>5 minutes)", severity: "warning" });
1181
1324
  }
1182
1325
  }
1183
- return errors;
1184
- };
1185
- var validateSQLQueryConfig = (config) => {
1186
- const errors = [];
1187
- if (!config) {
1188
- errors.push({
1189
- field: "sqlConfig",
1190
- message: "SQL query configuration is required",
1191
- severity: "error"
1192
- });
1193
- return errors;
1194
- }
1195
- if (!config.sql || config.sql.trim().length === 0) {
1196
- errors.push({
1197
- field: "sqlConfig.sql",
1198
- message: "SQL query is required",
1199
- severity: "error"
1200
- });
1201
- } else {
1202
- const sql = config.sql.trim().toLowerCase();
1203
- const dangerousKeywords = ["drop", "truncate", "alter", "create", "grant", "revoke"];
1204
- const hasDangerousKeyword = dangerousKeywords.some((keyword) => sql.includes(keyword + " "));
1205
- if (hasDangerousKeyword) {
1206
- errors.push({
1207
- field: "sqlConfig.sql",
1208
- message: "SQL query contains potentially dangerous operations (DROP, TRUNCATE, ALTER, etc.)",
1209
- severity: "error"
1210
- });
1326
+ const maxRows = c.maxRows;
1327
+ if (typeof maxRows === "number") {
1328
+ if (maxRows < 1) {
1329
+ errors.push(err("maxRows", "Max rows must be at least 1"));
1330
+ } else if (maxRows > 1e4) {
1331
+ warnings.push({ field: "maxRows", message: "Max rows is very high (>10,000). This may impact performance.", severity: "warning" });
1211
1332
  }
1212
1333
  }
1213
- return errors;
1334
+ return { valid: errors.length === 0, errors, warnings };
1214
1335
  };
1215
1336
  function isQueryableConnector(connector) {
1216
1337
  if (connector.isQueryable !== undefined) {
@@ -1242,17 +1363,26 @@ var BuildQueryContractSchema = {
1242
1363
  mode: {
1243
1364
  type: "select",
1244
1365
  required: true,
1245
- options: ["visual", "sql"],
1246
- description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql"
1366
+ options: QUERY_MODES,
1367
+ description: "Always 'visual': the Query step is built with the visual builder and described in visualConfig. Hand-written SQL is not accepted."
1247
1368
  },
1248
1369
  queryType: {
1249
1370
  type: "select",
1250
1371
  required: true,
1251
- options: ["select", "insert", "update", "delete", "custom"],
1252
- description: "Only select is a read; the write types exist for the SQL mode of database connectors"
1372
+ options: QUERY_TYPES,
1373
+ description: "Always 'select': the Query step reads data. Use a Transform step to write."
1374
+ },
1375
+ visualConfig: {
1376
+ type: "object",
1377
+ required: false,
1378
+ description: 'The query. tables: the table or object to read; fields: "table.column" names to return; ' + "conditions: filters { field, operator, value } (or valueField to compare two fields, or operator " + "'inRelative' with relative { direction: last|next|this, amount, unit: day|week|month|quarter|year } for date windows), " + `which may be grouped { type: 'group', conditions } up to ${MAX_CONDITION_DEPTH} levels deep and joined with AND/OR; ` + "distinct; groupBy; aggregates [{ fn: COUNT|COUNT_DISTINCT|SUM|AVG|MIN|MAX, field, alias }]; " + "having: filters on aggregate aliases or groupBy columns; sort [{ field, direction }]; maxRows: the run cap (up to 10,000)."
1379
+ },
1380
+ sqlConfig: {
1381
+ type: "object",
1382
+ required: false,
1383
+ derived: true,
1384
+ description: "Compiled from visualConfig by the editor or the server. Never write it — describe the query in visualConfig."
1253
1385
  },
1254
- visualConfig: { type: "object", required: false },
1255
- sqlConfig: { type: "object", required: false },
1256
1386
  timeout: { type: "number", required: false },
1257
1387
  maxRows: { type: "number", required: false }
1258
1388
  };
@@ -1261,11 +1391,11 @@ var BuildQueryContractSchema = {
1261
1391
  var queryDefinition = {
1262
1392
  id: "query",
1263
1393
  name: "Query",
1264
- description: "Execute queries against connected databases, APIs, and CRMs",
1265
- longDescription: "Create powerful queries using either a visual no-code builder or advanced SQL editor. Supports databases (PostgreSQL, MySQL, MongoDB, etc.), APIs (REST, GraphQL), and CRMs (Salesforce, HubSpot). Query results can be used as input for downstream actions.",
1394
+ description: "Read records from a connected database, app or CRM",
1395
+ longDescription: "Pick a table or object, the fields you want and any filters, and the Query step reads the matching records. Filters can be grouped with AND / OR, compare two fields, or use a date window such as the last 30 days. You can also remove duplicates, count, total or average values by group, filter those totals, and sort the result. Works with databases (PostgreSQL, MySQL, SQL Server, MongoDB and more), apps (QuickBooks, HubSpot, Jira, Shopify, Google Sheets and more) and CRMs (Salesforce). The records it returns can be used by the steps that follow.",
1266
1396
  category: "database" /* Database */,
1267
1397
  type: "query" /* Query */,
1268
- tags: ["query", "database", "sql", "visual-builder", "no-code", "api", "crm", "built-in"],
1398
+ tags: ["query", "database", "visual-builder", "no-code", "filter", "api", "crm", "built-in"],
1269
1399
  status: "stable" /* Stable */,
1270
1400
  icon: "QueryQueue",
1271
1401
  color: "#0043ce",
@@ -1283,17 +1413,12 @@ var queryDefinition = {
1283
1413
  key: "mode",
1284
1414
  label: "Query Mode",
1285
1415
  type: "select" /* Select */,
1286
- description: "Choose between visual query builder or SQL editor",
1416
+ description: "Always 'visual' — the query is built with the visual builder",
1287
1417
  options: [
1288
1418
  {
1289
- label: "Visual Builder (No-Code)",
1419
+ label: "Visual Builder",
1290
1420
  value: "visual" /* Visual */,
1291
- description: "Build queries visually without writing code"
1292
- },
1293
- {
1294
- label: "SQL Editor (Advanced)",
1295
- value: "sql" /* SQL */,
1296
- description: "Write raw SQL queries with syntax highlighting"
1421
+ description: "Pick the table, fields and filters — no code needed"
1297
1422
  }
1298
1423
  ],
1299
1424
  defaultValue: "visual" /* Visual */,
@@ -1341,7 +1466,7 @@ var queryDefinition = {
1341
1466
  key: "visualConfig.fields",
1342
1467
  label: "Select Fields",
1343
1468
  type: "array" /* Array */,
1344
- description: "Choose fields to include in the query result",
1469
+ description: 'The fields to return, as "table.column". Not needed for a summary query (aggregates / groupBy) or when every table and field is selected.',
1345
1470
  validation: { required: true },
1346
1471
  conditional: [
1347
1472
  {
@@ -1384,39 +1509,6 @@ var queryDefinition = {
1384
1509
  }
1385
1510
  ]
1386
1511
  },
1387
- {
1388
- key: "sqlConfig.sql",
1389
- label: "SQL Query",
1390
- type: "code" /* Code */,
1391
- language: "sql",
1392
- description: "Write your SQL query here",
1393
- placeholder: "SELECT * FROM users WHERE status = 'active' LIMIT 100",
1394
- validation: { required: true },
1395
- conditional: [
1396
- {
1397
- field: "mode",
1398
- operator: "==",
1399
- value: "sql" /* SQL */,
1400
- action: "show"
1401
- }
1402
- ]
1403
- },
1404
- {
1405
- key: "sqlConfig.parameters",
1406
- label: "Query Parameters",
1407
- type: "json" /* JSON */,
1408
- description: 'Parameters for parameterized queries (e.g., {"userId": 123})',
1409
- validation: { required: false },
1410
- advanced: true,
1411
- conditional: [
1412
- {
1413
- field: "mode",
1414
- operator: "==",
1415
- value: "sql" /* SQL */,
1416
- action: "show"
1417
- }
1418
- ]
1419
- },
1420
1512
  {
1421
1513
  key: "timeout",
1422
1514
  label: "Query Timeout (ms)",
@@ -1430,7 +1522,7 @@ var queryDefinition = {
1430
1522
  key: "maxRows",
1431
1523
  label: "Max Rows",
1432
1524
  type: "number" /* Number */,
1433
- description: "Legacy step-level field — NOT read at run. Cap a visual Query with visualConfig.maxRows; in SQL mode write a LIMIT.",
1525
+ description: "Legacy step-level field — not read at run. To cap a run, set visualConfig.maxRows.",
1434
1526
  defaultValue: 1000,
1435
1527
  validation: { required: false, min: 1, max: 1e4 },
1436
1528
  advanced: true
@@ -1446,7 +1538,7 @@ var queryDefinition = {
1446
1538
  {
1447
1539
  id: "query_mode",
1448
1540
  label: "Query Configuration",
1449
- description: "Configure how you want to build your query",
1541
+ description: "The query is built with the visual builder",
1450
1542
  fields: ["mode", "queryType"]
1451
1543
  },
1452
1544
  {
@@ -1455,12 +1547,6 @@ var queryDefinition = {
1455
1547
  description: "Build your query visually without writing code",
1456
1548
  fields: ["visualConfig.tables", "visualConfig.fields", "visualConfig.limit"]
1457
1549
  },
1458
- {
1459
- id: "sql_editor",
1460
- label: "SQL Editor",
1461
- description: "Write raw SQL queries",
1462
- fields: ["sqlConfig.sql", "sqlConfig.parameters"]
1463
- },
1464
1550
  {
1465
1551
  id: "settings",
1466
1552
  label: "Advanced Settings",
@@ -1500,7 +1586,7 @@ var queryDefinition = {
1500
1586
  {
1501
1587
  key: "generatedSQL",
1502
1588
  type: "string" /* String */,
1503
- description: "The SQL query that was executed (for visual builder mode)",
1589
+ description: "The query that ran, compiled from the visual builder",
1504
1590
  example: "SELECT id, name, email FROM users WHERE status = 'active' LIMIT 100"
1505
1591
  }
1506
1592
  ],
@@ -1626,6 +1712,32 @@ var aggregateDefinition = {
1626
1712
  testable: false
1627
1713
  };
1628
1714
 
1715
+ // src/catalog/storage/fileHelp.ts
1716
+ var FILE_OUTPUT_HELP = {
1717
+ operation: "The operation that ran",
1718
+ data: "File contents, for text files such as CSV, JSON or TXT",
1719
+ s3Key: "The file itself, for PDF, Excel, images and other non-text files. Pass it to Excel, CSV, IDP or an Upload step.",
1720
+ fileName: "File name",
1721
+ mimeType: "File type, e.g. text/csv or application/pdf",
1722
+ path: "Where the file was read from"
1723
+ };
1724
+ var FILE_INPUT_HELP = {
1725
+ uploadContent: "Leave blank to upload the file from the step before, or pick a value from an earlier step, or type the content.",
1726
+ formatContent: (format) => `The ${format} from an earlier step (a download or a generated file), or paste the ${format} here.`,
1727
+ excelContent: "The Excel file from an earlier step (a download or a generated file)."
1728
+ };
1729
+ var STORAGE_DOWNLOAD_OUTPUT_SCHEMA = {
1730
+ type: "object",
1731
+ properties: {
1732
+ operation: { type: "string", description: FILE_OUTPUT_HELP.operation },
1733
+ data: { type: "string", description: FILE_OUTPUT_HELP.data },
1734
+ s3Key: { type: "string", description: FILE_OUTPUT_HELP.s3Key },
1735
+ fileName: { type: "string", description: FILE_OUTPUT_HELP.fileName },
1736
+ mimeType: { type: "string", description: FILE_OUTPUT_HELP.mimeType },
1737
+ path: { type: "string", description: FILE_OUTPUT_HELP.path }
1738
+ }
1739
+ };
1740
+
1629
1741
  // src/catalog/storage/csv-action/index.ts
1630
1742
  var csvActionDefinition = {
1631
1743
  id: "csv_action",
@@ -1640,7 +1752,7 @@ var csvActionDefinition = {
1640
1752
  inputSchema: {
1641
1753
  fields: [
1642
1754
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Parse (CSV → Records)", value: "parse" }, { label: "Generate (Records → CSV)", value: "generate" }, { label: "Edit existing file", value: "edit" }], defaultValue: "parse", validation: { required: true } },
1643
- { key: "content", label: "CSV Content", type: "string" /* String */, description: "Paste CSV content or use FX token from upstream step" },
1755
+ { key: "content", label: "CSV Content", type: "string" /* String */, description: FILE_INPUT_HELP.formatContent("CSV") },
1644
1756
  { key: "delimiter", label: "Delimiter", type: "select" /* Select */, options: [{ label: "Comma (,)", value: "," }, { label: "Semicolon (;)", value: ";" }, { label: "Tab", value: "\t" }, { label: "Pipe (|)", value: "|" }], defaultValue: "," },
1645
1757
  { key: "headerRow", label: "Header Row", type: "number" /* Number */, description: "Row number containing column headers (default: 1)", defaultValue: 1 },
1646
1758
  { key: "encoding", label: "Encoding", type: "select" /* Select */, options: [{ label: "UTF-8", value: "utf-8" }, { label: "Latin-1", value: "latin1" }, { label: "ASCII", value: "ascii" }], defaultValue: "utf-8" },
@@ -1680,7 +1792,7 @@ var jsonActionDefinition = {
1680
1792
  fields: [
1681
1793
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Parse (JSON → Records)", value: "parse" }, { label: "Generate (Records → JSON)", value: "generate" }, { label: "Edit existing file", value: "edit" }], defaultValue: "parse", validation: { required: true } },
1682
1794
  { key: "format", label: "Format", type: "select" /* Select */, options: [{ label: "JSON (array)", value: "json" }, { label: "JSONL (one object per line)", value: "jsonl" }], defaultValue: "json" },
1683
- { key: "content", label: "JSON Content", type: "string" /* String */, description: "Paste JSON content or use FX token from upstream step" },
1795
+ { key: "content", label: "JSON Content", type: "string" /* String */, description: FILE_INPUT_HELP.formatContent("JSON") },
1684
1796
  { key: "prettyPrint", label: "Pretty Print", type: "boolean" /* Boolean */, description: "Format output with indentation", defaultValue: true },
1685
1797
  { key: "fileName", label: "Output File Name", type: "string" /* String */, defaultValue: "export.json" }
1686
1798
  ]
@@ -1716,7 +1828,7 @@ var xmlActionDefinition = {
1716
1828
  inputSchema: {
1717
1829
  fields: [
1718
1830
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Parse (XML → Records)", value: "parse" }, { label: "Generate (Records → XML)", value: "generate" }, { label: "Edit existing file", value: "edit" }], defaultValue: "parse", validation: { required: true } },
1719
- { key: "content", label: "XML Content", type: "string" /* String */, description: "Paste XML or use FX token" },
1831
+ { key: "content", label: "XML Content", type: "string" /* String */, description: FILE_INPUT_HELP.formatContent("XML") },
1720
1832
  { key: "rootElement", label: "Root Element", type: "string" /* String */, description: "Root XML element name (for generate)", defaultValue: "data" },
1721
1833
  { key: "rowElement", label: "Row Element", type: "string" /* String */, description: "Element name per record (for generate)", defaultValue: "row" },
1722
1834
  { key: "fileName", label: "Output File Name", type: "string" /* String */, defaultValue: "export.xml" }
@@ -1753,7 +1865,7 @@ var excelActionDefinition = {
1753
1865
  inputSchema: {
1754
1866
  fields: [
1755
1867
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Parse (Excel → Records)", value: "parse" }, { label: "Generate (Records → Excel)", value: "generate" }, { label: "Edit existing file", value: "edit" }], defaultValue: "parse", validation: { required: true } },
1756
- { key: "content", label: "Excel File", type: "string" /* String */, description: "S3 key of uploaded Excel file or FX token from upstream" },
1868
+ { key: "content", label: "Excel File", type: "string" /* String */, description: FILE_INPUT_HELP.excelContent },
1757
1869
  { key: "sheetName", label: "Sheet Name", type: "string" /* String */, description: "Name of sheet to read (default: first sheet)" },
1758
1870
  { key: "headerRow", label: "Header Row", type: "number" /* Number */, description: "Row number containing headers", defaultValue: 1 },
1759
1871
  { key: "fileName", label: "Output File Name", type: "string" /* String */, defaultValue: "export.xlsx" }
@@ -1765,7 +1877,7 @@ var excelActionDefinition = {
1765
1877
  { key: "data", type: "array" /* Array */, description: "Parsed records (parse mode) — loop these with a ForEach. File metadata in generate mode." },
1766
1878
  { key: "columns", type: "json" /* JSON */, description: "Column names" },
1767
1879
  { key: "rowCount", type: "number" /* Number */, description: "Number of records" },
1768
- { key: "s3Key", type: "string" /* String */, description: "S3 key of generated file" }
1880
+ { key: "s3Key", type: "string" /* String */, description: "The generated Excel file. Pass it to an Upload step to save it." }
1769
1881
  ],
1770
1882
  sampleOutput: { data: [{ id: 1, name: "Widget" }], columns: ["id", "name"], rowCount: 1 }
1771
1883
  },
@@ -1795,7 +1907,7 @@ var s3ActionDefinition = {
1795
1907
  { key: "bucket", label: "Bucket Name", type: "string" /* String */, description: "S3 bucket name (defaults to the connector’s bucket)" },
1796
1908
  { key: "key", label: "Object Key (Path)", type: "string" /* String */, description: "S3 object key, e.g. data/reports/monthly.csv" },
1797
1909
  { key: "prefix", label: "Prefix (for List)", type: "string" /* String */, description: "Filter objects by prefix (folder path)" },
1798
- { key: "content", label: "File Content (for Upload)", type: "string" /* String */, description: "Content to upload — use FX token from upstream step" },
1910
+ { key: "content", label: "File Content (for Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent },
1799
1911
  { key: "contentType", label: "Content Type", type: "string" /* String */, description: "MIME type (e.g. text/csv, application/json)", defaultValue: "application/octet-stream" },
1800
1912
  { key: "destBucket", label: "Destination Bucket (for Copy)", type: "string" /* String */ },
1801
1913
  { key: "destKey", label: "Destination Key (for Copy)", type: "string" /* String */ },
@@ -1836,7 +1948,7 @@ var ftpActionDefinition = {
1836
1948
  { key: "connector", label: "FTP / SFTP Connection", type: "connection" /* Connection */, description: "Select a configured FTP/SFTP connector from Integrations", validation: { required: true } },
1837
1949
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Download File", value: "download" }, { label: "Upload File", value: "upload" }, { label: "List Files", value: "list" }, { label: "Delete File", value: "delete" }, { label: "Move / Rename", value: "move" }], defaultValue: "download", validation: { required: true } },
1838
1950
  { key: "remotePath", label: "Remote Path", type: "string" /* String */, description: "File or folder path on the server (use file explorer to browse)" },
1839
- { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: "Content to upload — use FX token from upstream step" },
1951
+ { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent },
1840
1952
  { key: "fileName", label: "File Name (Upload)", type: "string" /* String */, description: "Name for the uploaded file" },
1841
1953
  { key: "destPath", label: "Destination Path (Move)", type: "string" /* String */, description: "New path for moved/renamed file" },
1842
1954
  { key: "ifExists", label: "If File Exists", type: "select" /* Select */, options: [{ label: "Overwrite", value: "overwrite" }, { label: "Skip", value: "skip" }], defaultValue: "overwrite" }
@@ -1877,7 +1989,7 @@ var googleDriveActionDefinition = {
1877
1989
  { key: "fileId", label: "File ID / Path", type: "string" /* String */, description: "Google Drive file ID (use file explorer to browse)" },
1878
1990
  { key: "folderId", label: "Folder ID", type: "string" /* String */, description: "Target folder (root if empty)" },
1879
1991
  { key: "fileName", label: "File Name", type: "string" /* String */ },
1880
- { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: "Content to upload — use FX token from upstream" },
1992
+ { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent },
1881
1993
  { key: "mimeType", label: "MIME Type", type: "string" /* String */, defaultValue: "application/octet-stream" },
1882
1994
  { key: "destFolderId", label: "Destination Folder (Move/Copy)", type: "string" /* String */ },
1883
1995
  { key: "shareEmail", label: "Share With Email", type: "string" /* String */ },
@@ -1919,7 +2031,7 @@ var dropboxActionDefinition = {
1919
2031
  { key: "connector", label: "Dropbox Account", type: "connection" /* Connection */, description: "Select a connected Dropbox account", validation: { required: true } },
1920
2032
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Download File", value: "download" }, { label: "Upload File", value: "upload" }, { label: "List Files", value: "list" }, { label: "Delete", value: "delete" }, { label: "Move", value: "move" }, { label: "Copy", value: "copy" }, { label: "Create Folder", value: "create_folder" }, { label: "Get Shared Link", value: "shared_link" }], defaultValue: "download", validation: { required: true } },
1921
2033
  { key: "path", label: "File / Folder Path", type: "string" /* String */, description: "Path on Dropbox (use file explorer to browse)" },
1922
- { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: "Content to upload — use FX token from upstream" },
2034
+ { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent },
1923
2035
  { key: "fileName", label: "File Name (Upload)", type: "string" /* String */ },
1924
2036
  { key: "destPath", label: "Destination Path (Move/Copy)", type: "string" /* String */ },
1925
2037
  { key: "mode", label: "Upload Mode", type: "select" /* Select */, options: [{ label: "Add (fail if exists)", value: "add" }, { label: "Overwrite", value: "overwrite" }], defaultValue: "overwrite" }
@@ -1959,7 +2071,7 @@ var onedriveActionDefinition = {
1959
2071
  { key: "operation", label: "Operation", type: "select" /* Select */, options: [{ label: "Download File", value: "download" }, { label: "Upload File", value: "upload" }, { label: "List Files", value: "list" }, { label: "Delete", value: "delete" }, { label: "Move", value: "move" }, { label: "Copy", value: "copy" }], defaultValue: "download", validation: { required: true } },
1960
2072
  { key: "path", label: "File / Folder Path", type: "string" /* String */, description: "Path on OneDrive (use file explorer to browse)" },
1961
2073
  { key: "itemId", label: "Item ID", type: "string" /* String */, description: "OneDrive item ID (alternative to path)" },
1962
- { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: "Content to upload — use FX token from upstream" },
2074
+ { key: "content", label: "File Content (Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent },
1963
2075
  { key: "fileName", label: "File Name (Upload)", type: "string" /* String */ },
1964
2076
  { key: "destPath", label: "Destination Path (Move/Copy)", type: "string" /* String */ }
1965
2077
  ]
@@ -2017,14 +2129,17 @@ var storageActionDefinition = {
2017
2129
  },
2018
2130
  { key: "path", label: "Path", type: "string" /* String */, description: "File or folder path — use the file browser in the panel." },
2019
2131
  { key: "destPath", label: "Destination path", type: "string" /* String */, description: "Target for copy / move." },
2020
- { key: "content", label: "File content (for Upload)", type: "string" /* String */, description: "Content to upload — bind an FX token from an upstream step." }
2132
+ { key: "content", label: "File content (for Upload)", type: "string" /* String */, description: FILE_INPUT_HELP.uploadContent }
2021
2133
  ]
2022
2134
  },
2023
2135
  outputSchema: {
2024
2136
  type: "object",
2025
2137
  properties: [
2026
2138
  { key: "operation", type: "string" /* String */, description: "The operation that ran" },
2027
- { key: "data", type: "json" /* JSON */, description: "Operation result or downloaded content" },
2139
+ { key: "data", type: "json" /* JSON */, description: `Download: ${FILE_OUTPUT_HELP.data}. Other operations: their result.` },
2140
+ { key: "s3Key", type: "string" /* String */, description: `Download: ${FILE_OUTPUT_HELP.s3Key}` },
2141
+ { key: "fileName", type: "string" /* String */, description: `Download: ${FILE_OUTPUT_HELP.fileName}` },
2142
+ { key: "mimeType", type: "string" /* String */, description: `Download: ${FILE_OUTPUT_HELP.mimeType}` },
2028
2143
  { key: "files", type: "json" /* JSON */, description: "Listing (for the List operation)" },
2029
2144
  { key: "path", type: "string" /* String */, description: "Affected file/folder path" },
2030
2145
  { key: "url", type: "string" /* String */, description: "Signed URL (for the Get signed URL operation)" }
@@ -2666,6 +2781,10 @@ function isSatisfiedOutOfBand(actionId, fieldKey, config) {
2666
2781
  return true;
2667
2782
  return Array.isArray(config.blocks) && config.blocks.length > 0;
2668
2783
  }
2784
+ if (actionId === "query" && fieldKey === "visualConfig.fields")
2785
+ return queryFieldsSatisfied(config);
2786
+ if (actionId === "query" && fieldKey === "visualConfig.tables")
2787
+ return queryTablesSatisfied(config);
2669
2788
  return false;
2670
2789
  }
2671
2790
  var MESSAGE_BODY_ACTIONS = new Set([
@@ -3560,9 +3679,6 @@ var EmailConfig = {
3560
3679
  }
3561
3680
  }
3562
3681
  };
3563
- // src/catalog/isOneOf.ts
3564
- var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3565
-
3566
3682
  // src/catalog/general/transform/config.ts
3567
3683
  var TRANSFORM_MODES = ["visual", "expression"];
3568
3684
  var TRANSFORM_OPERATIONS = [
@@ -4470,10 +4586,10 @@ var FILE_EDIT_SCHEMA = {
4470
4586
  matchOp: { type: "select", required: false, options: FILE_EDIT_MATCH_OPS, description: 'set_fields: comparison; "=" by default' },
4471
4587
  matchValue: { type: "string", required: false, description: "set_fields: value compared against" },
4472
4588
  setFields: { type: "object", required: false, description: "set_fields: { column: newValue } applied to matching rows" },
4473
- destination: { type: "select", required: false, options: FILE_EDIT_DESTINATIONS, description: '"new" writes an edited copy (default); "in_place" overwrites an S3 source' },
4589
+ destination: { type: "select", required: false, options: FILE_EDIT_DESTINATIONS, description: '"new" writes an edited copy (default); "in_place" overwrites the source file' },
4474
4590
  outputFileName: { type: "string", required: false, description: 'Name of the edited copy when destination is "new"' },
4475
4591
  source: { type: "select", required: false, options: FILE_EDIT_SOURCES, description: "Where the file to edit comes from; inferred from content when absent" },
4476
- s3Key: { type: "string", required: false, description: 'source "s3": platform-bucket key of the file to edit' },
4592
+ s3Key: { type: "string", required: false, description: `source "s3": an earlier step's file (its s3Key output) to edit` },
4477
4593
  url: { type: "url", required: false, description: 'source "url": the file to edit is fetched from here' }
4478
4594
  };
4479
4595
  function fileEditErrors(config) {
@@ -4505,7 +4621,7 @@ var CsvActionConfig = {
4505
4621
  },
4506
4622
  schema: {
4507
4623
  operation: { type: "select", required: true, options: FILE_FORMAT_OPERATIONS, description: "parse (CSV → records), generate (records → CSV) or edit an existing file" },
4508
- content: { type: "text", required: false, description: "CSV text, or an FX token from an upstream step" },
4624
+ content: { type: "text", required: false, description: "CSV text, or a reference to an earlier step's file content" },
4509
4625
  delimiter: { type: "select", required: false, options: [",", ";", "\t", "|"] },
4510
4626
  headerRow: { type: "number", required: false, min: 1, description: "1-based row that holds the column headers" },
4511
4627
  encoding: { type: "string", required: false, description: "Text encoding (utf-8 by default)" },
@@ -4531,7 +4647,7 @@ var JsonActionConfig = {
4531
4647
  schema: {
4532
4648
  operation: { type: "select", required: true, options: FILE_FORMAT_OPERATIONS, description: "parse (JSON → records), generate (records → JSON) or edit an existing file" },
4533
4649
  format: { type: "select", required: false, options: ["json", "jsonl"], description: "json (array) or jsonl (one object per line)" },
4534
- content: { type: "text", required: false, description: "JSON text, or an FX token from an upstream step" },
4650
+ content: { type: "text", required: false, description: "JSON text, or a reference to an earlier step's file content" },
4535
4651
  prettyPrint: { type: "boolean", required: false, description: "generate: indent the output" },
4536
4652
  fileName: { type: "string", required: false, description: "generate: name of the produced file" },
4537
4653
  jsonl: { type: "boolean", required: false, description: "edit: treat the file as JSONL" },
@@ -4558,7 +4674,7 @@ var XmlActionConfig = {
4558
4674
  },
4559
4675
  schema: {
4560
4676
  operation: { type: "select", required: true, options: FILE_FORMAT_OPERATIONS, description: "parse (XML → records), generate (records → XML) or edit an existing file" },
4561
- content: { type: "text", required: false, description: "XML text, or an FX token from an upstream step" },
4677
+ content: { type: "text", required: false, description: "XML text, or a reference to an earlier step's file content" },
4562
4678
  rootElement: { type: "string", required: false, description: "generate: root element name" },
4563
4679
  rowElement: { type: "string", required: false, description: "Element that holds one record" },
4564
4680
  fileName: { type: "string", required: false, description: "generate: name of the produced file" },
@@ -4580,7 +4696,7 @@ var ExcelActionConfig = {
4580
4696
  },
4581
4697
  schema: {
4582
4698
  operation: { type: "select", required: true, options: FILE_FORMAT_OPERATIONS, description: "parse (Excel → records), generate (records → Excel) or edit an existing file" },
4583
- content: { type: "text", required: false, description: "Platform-bucket key of the workbook, base64 bytes, or an FX token from an upstream step" },
4699
+ content: { type: "text", required: false, description: "A reference to an earlier step's Excel file (its s3Key output), or base64 bytes" },
4584
4700
  sheetName: { type: "string", required: false, description: "Sheet to read; the first sheet when absent" },
4585
4701
  headerRow: { type: "number", required: false, min: 1, description: "1-based row that holds the column headers" },
4586
4702
  fileName: { type: "string", required: false, description: "generate: name of the produced file" },
@@ -4598,7 +4714,7 @@ var FIXED_SCHEMA = {
4598
4714
  connector: { type: "connection", required: true, description: "Storage connector instance; its provider decides the operations" },
4599
4715
  operation: { type: "select", required: true, description: "Per provider: list, download, upload, delete, copy, move, presign, share, create_folder, transfer, sync" },
4600
4716
  outputAs: { type: "string", required: false, description: "Name of this step's output in the run's data bag" },
4601
- content: { type: "text", required: false, description: "upload: the body" },
4717
+ content: { type: "text", required: false, description: "upload: the file content; blank uploads the file the step before produced" },
4602
4718
  destConnector: { type: "connection", required: false, description: "transfer / sync: destination connector instance" },
4603
4719
  destPath: { type: "string", required: false, description: "transfer / sync: destination path; also move / copy destination on path-based providers" },
4604
4720
  sourceIsDirectory: { type: "boolean", required: false, description: "transfer: copy a folder recursively" },
@@ -5980,7 +6096,6 @@ export {
5980
6096
  ActionStatus,
5981
6097
  ActionType,
5982
6098
  AggregateConfig,
5983
- AggregateFunction,
5984
6099
  AiAgentConfig,
5985
6100
  CONDITION_OPERATORS,
5986
6101
  CONFIG_CONTRACTS,
@@ -6002,6 +6117,8 @@ export {
6002
6117
  FILE_EDIT_SCHEMA,
6003
6118
  FILE_EDIT_SOURCES,
6004
6119
  FILE_FORMAT_OPERATIONS,
6120
+ FILE_INPUT_HELP,
6121
+ FILE_OUTPUT_HELP,
6005
6122
  FTP_OPERATIONS,
6006
6123
  FieldType,
6007
6124
  ForEachConfig,
@@ -6011,22 +6128,33 @@ export {
6011
6128
  GroupConfig,
6012
6129
  IDP_EXTRACT_STRATEGIES,
6013
6130
  IdpExtractConfig,
6014
- JoinType,
6015
6131
  JsonActionConfig,
6016
- LogicalOperator,
6017
6132
  MAPPING_FUNCTIONS,
6018
6133
  MAPPING_FUNCTION_SPECS,
6134
+ MAX_CONDITION_DEPTH,
6019
6135
  MAX_STEP_CONFIG_BYTES,
6020
6136
  ManualTriggerConfig,
6021
6137
  NodeStatus,
6022
6138
  ONEDRIVE_OPERATIONS,
6023
6139
  OneDriveActionConfig,
6024
6140
  PLATFORM_CONFIG_KEYS,
6141
+ QUERY_AGGREGATE_FUNCTIONS,
6142
+ QUERY_AUTHORED_SQL_MESSAGE,
6143
+ QUERY_COMPARISON_OPERATORS,
6144
+ QUERY_CONDITION_OPERATORS,
6145
+ QUERY_MODES,
6146
+ QUERY_RELATIVE_DATE_OPERATORS,
6147
+ QUERY_TYPES,
6148
+ QUERY_VISUAL_ONLY_MESSAGE,
6025
6149
  QueryMode,
6026
6150
  QueryType,
6151
+ RELATIVE_DATE_DIRECTIONS,
6152
+ RELATIVE_DATE_MAX_AMOUNT,
6153
+ RELATIVE_DATE_UNITS,
6027
6154
  S3ActionConfig,
6028
6155
  S3_OPERATIONS,
6029
6156
  STORAGE_CONNECTOR_IDS,
6157
+ STORAGE_DOWNLOAD_OUTPUT_SCHEMA,
6030
6158
  SortDirection,
6031
6159
  StorageActionConfig,
6032
6160
  TRANSFORM_MODES,
@@ -6066,6 +6194,7 @@ export {
6066
6194
  getStorageProvider,
6067
6195
  groupKeyName,
6068
6196
  inferSchemaFromSample,
6197
+ isConditionGroup,
6069
6198
  isEditorPrivateKey,
6070
6199
  isEventCapableConnector,
6071
6200
  isMappingFunction,
@@ -6073,6 +6202,8 @@ export {
6073
6202
  mappingTransformFunctionName,
6074
6203
  migrateDelayConfig,
6075
6204
  queryDefinition,
6205
+ queryFieldsSatisfied,
6206
+ queryTablesSatisfied,
6076
6207
  resolveRecordSchema,
6077
6208
  selectsWithoutValues,
6078
6209
  storageConfigKeys,
@@ -6081,11 +6212,13 @@ export {
6081
6212
  transformConnectionToEdge,
6082
6213
  transformFlowToWorkflow,
6083
6214
  transformStepToNode,
6215
+ validateBuildQueryConfig,
6084
6216
  validateConnectorTriggerEvents,
6085
6217
  validateEdge,
6086
6218
  validateFlowData,
6087
6219
  validateNode,
6088
6220
  validateStepConfig,
6089
6221
  validateStepConfigs,
6222
+ validateVisualConfig,
6090
6223
  validateWorkflow
6091
6224
  };