@octaviaflow/flow-rules 0.18.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
  ],
@@ -2695,6 +2781,10 @@ function isSatisfiedOutOfBand(actionId, fieldKey, config) {
2695
2781
  return true;
2696
2782
  return Array.isArray(config.blocks) && config.blocks.length > 0;
2697
2783
  }
2784
+ if (actionId === "query" && fieldKey === "visualConfig.fields")
2785
+ return queryFieldsSatisfied(config);
2786
+ if (actionId === "query" && fieldKey === "visualConfig.tables")
2787
+ return queryTablesSatisfied(config);
2698
2788
  return false;
2699
2789
  }
2700
2790
  var MESSAGE_BODY_ACTIONS = new Set([
@@ -3589,9 +3679,6 @@ var EmailConfig = {
3589
3679
  }
3590
3680
  }
3591
3681
  };
3592
- // src/catalog/isOneOf.ts
3593
- var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3594
-
3595
3682
  // src/catalog/general/transform/config.ts
3596
3683
  var TRANSFORM_MODES = ["visual", "expression"];
3597
3684
  var TRANSFORM_OPERATIONS = [
@@ -6009,7 +6096,6 @@ export {
6009
6096
  ActionStatus,
6010
6097
  ActionType,
6011
6098
  AggregateConfig,
6012
- AggregateFunction,
6013
6099
  AiAgentConfig,
6014
6100
  CONDITION_OPERATORS,
6015
6101
  CONFIG_CONTRACTS,
@@ -6042,19 +6128,29 @@ export {
6042
6128
  GroupConfig,
6043
6129
  IDP_EXTRACT_STRATEGIES,
6044
6130
  IdpExtractConfig,
6045
- JoinType,
6046
6131
  JsonActionConfig,
6047
- LogicalOperator,
6048
6132
  MAPPING_FUNCTIONS,
6049
6133
  MAPPING_FUNCTION_SPECS,
6134
+ MAX_CONDITION_DEPTH,
6050
6135
  MAX_STEP_CONFIG_BYTES,
6051
6136
  ManualTriggerConfig,
6052
6137
  NodeStatus,
6053
6138
  ONEDRIVE_OPERATIONS,
6054
6139
  OneDriveActionConfig,
6055
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,
6056
6149
  QueryMode,
6057
6150
  QueryType,
6151
+ RELATIVE_DATE_DIRECTIONS,
6152
+ RELATIVE_DATE_MAX_AMOUNT,
6153
+ RELATIVE_DATE_UNITS,
6058
6154
  S3ActionConfig,
6059
6155
  S3_OPERATIONS,
6060
6156
  STORAGE_CONNECTOR_IDS,
@@ -6098,6 +6194,7 @@ export {
6098
6194
  getStorageProvider,
6099
6195
  groupKeyName,
6100
6196
  inferSchemaFromSample,
6197
+ isConditionGroup,
6101
6198
  isEditorPrivateKey,
6102
6199
  isEventCapableConnector,
6103
6200
  isMappingFunction,
@@ -6105,6 +6202,8 @@ export {
6105
6202
  mappingTransformFunctionName,
6106
6203
  migrateDelayConfig,
6107
6204
  queryDefinition,
6205
+ queryFieldsSatisfied,
6206
+ queryTablesSatisfied,
6108
6207
  resolveRecordSchema,
6109
6208
  selectsWithoutValues,
6110
6209
  storageConfigKeys,
@@ -6113,11 +6212,13 @@ export {
6113
6212
  transformConnectionToEdge,
6114
6213
  transformFlowToWorkflow,
6115
6214
  transformStepToNode,
6215
+ validateBuildQueryConfig,
6116
6216
  validateConnectorTriggerEvents,
6117
6217
  validateEdge,
6118
6218
  validateFlowData,
6119
6219
  validateNode,
6120
6220
  validateStepConfig,
6121
6221
  validateStepConfigs,
6222
+ validateVisualConfig,
6122
6223
  validateWorkflow
6123
6224
  };