@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/catalog/general/query/config.d.ts +43 -16
- package/dist/catalog/general/query/index.d.ts +4 -4
- package/dist/catalog/general/query/types.d.ts +157 -79
- package/dist/index.cjs +319 -218
- package/dist/index.d.ts +2 -2
- package/dist/index.js +319 -218
- package/package.json +1 -1
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
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1036
|
-
(
|
|
1037
|
-
|
|
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
|
-
|
|
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
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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 (!
|
|
1090
|
-
|
|
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 (
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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 (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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 (
|
|
1119
|
-
if (
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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 (
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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:
|
|
1246
|
-
description: "visual: the
|
|
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:
|
|
1252
|
-
description: "
|
|
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: "
|
|
1265
|
-
longDescription: "
|
|
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", "
|
|
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: "
|
|
1416
|
+
description: "Always 'visual' — the query is built with the visual builder",
|
|
1287
1417
|
options: [
|
|
1288
1418
|
{
|
|
1289
|
-
label: "Visual Builder
|
|
1419
|
+
label: "Visual Builder",
|
|
1290
1420
|
value: "visual" /* Visual */,
|
|
1291
|
-
description: "
|
|
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:
|
|
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 —
|
|
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: "
|
|
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
|
|
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
|
};
|