@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.cjs
CHANGED
|
@@ -45,7 +45,6 @@ __export(exports_src, {
|
|
|
45
45
|
ActionStatus: () => ActionStatus,
|
|
46
46
|
ActionType: () => ActionType,
|
|
47
47
|
AggregateConfig: () => AggregateConfig,
|
|
48
|
-
AggregateFunction: () => AggregateFunction,
|
|
49
48
|
AiAgentConfig: () => AiAgentConfig,
|
|
50
49
|
CONDITION_OPERATORS: () => CONDITION_OPERATORS,
|
|
51
50
|
CONFIG_CONTRACTS: () => CONFIG_CONTRACTS,
|
|
@@ -78,19 +77,29 @@ __export(exports_src, {
|
|
|
78
77
|
GroupConfig: () => GroupConfig,
|
|
79
78
|
IDP_EXTRACT_STRATEGIES: () => IDP_EXTRACT_STRATEGIES,
|
|
80
79
|
IdpExtractConfig: () => IdpExtractConfig,
|
|
81
|
-
JoinType: () => JoinType,
|
|
82
80
|
JsonActionConfig: () => JsonActionConfig,
|
|
83
|
-
LogicalOperator: () => LogicalOperator,
|
|
84
81
|
MAPPING_FUNCTIONS: () => MAPPING_FUNCTIONS,
|
|
85
82
|
MAPPING_FUNCTION_SPECS: () => MAPPING_FUNCTION_SPECS,
|
|
83
|
+
MAX_CONDITION_DEPTH: () => MAX_CONDITION_DEPTH,
|
|
86
84
|
MAX_STEP_CONFIG_BYTES: () => MAX_STEP_CONFIG_BYTES,
|
|
87
85
|
ManualTriggerConfig: () => ManualTriggerConfig,
|
|
88
86
|
NodeStatus: () => NodeStatus,
|
|
89
87
|
ONEDRIVE_OPERATIONS: () => ONEDRIVE_OPERATIONS,
|
|
90
88
|
OneDriveActionConfig: () => OneDriveActionConfig,
|
|
91
89
|
PLATFORM_CONFIG_KEYS: () => PLATFORM_CONFIG_KEYS,
|
|
90
|
+
QUERY_AGGREGATE_FUNCTIONS: () => QUERY_AGGREGATE_FUNCTIONS,
|
|
91
|
+
QUERY_AUTHORED_SQL_MESSAGE: () => QUERY_AUTHORED_SQL_MESSAGE,
|
|
92
|
+
QUERY_COMPARISON_OPERATORS: () => QUERY_COMPARISON_OPERATORS,
|
|
93
|
+
QUERY_CONDITION_OPERATORS: () => QUERY_CONDITION_OPERATORS,
|
|
94
|
+
QUERY_MODES: () => QUERY_MODES,
|
|
95
|
+
QUERY_RELATIVE_DATE_OPERATORS: () => QUERY_RELATIVE_DATE_OPERATORS,
|
|
96
|
+
QUERY_TYPES: () => QUERY_TYPES,
|
|
97
|
+
QUERY_VISUAL_ONLY_MESSAGE: () => QUERY_VISUAL_ONLY_MESSAGE,
|
|
92
98
|
QueryMode: () => QueryMode,
|
|
93
99
|
QueryType: () => QueryType,
|
|
100
|
+
RELATIVE_DATE_DIRECTIONS: () => RELATIVE_DATE_DIRECTIONS,
|
|
101
|
+
RELATIVE_DATE_MAX_AMOUNT: () => RELATIVE_DATE_MAX_AMOUNT,
|
|
102
|
+
RELATIVE_DATE_UNITS: () => RELATIVE_DATE_UNITS,
|
|
94
103
|
S3ActionConfig: () => S3ActionConfig,
|
|
95
104
|
S3_OPERATIONS: () => S3_OPERATIONS,
|
|
96
105
|
STORAGE_CONNECTOR_IDS: () => STORAGE_CONNECTOR_IDS,
|
|
@@ -134,6 +143,7 @@ __export(exports_src, {
|
|
|
134
143
|
getStorageProvider: () => getStorageProvider,
|
|
135
144
|
groupKeyName: () => groupKeyName,
|
|
136
145
|
inferSchemaFromSample: () => inferSchemaFromSample,
|
|
146
|
+
isConditionGroup: () => isConditionGroup,
|
|
137
147
|
isEditorPrivateKey: () => isEditorPrivateKey,
|
|
138
148
|
isEventCapableConnector: () => isEventCapableConnector,
|
|
139
149
|
isMappingFunction: () => isMappingFunction,
|
|
@@ -141,6 +151,8 @@ __export(exports_src, {
|
|
|
141
151
|
mappingTransformFunctionName: () => mappingTransformFunctionName,
|
|
142
152
|
migrateDelayConfig: () => migrateDelayConfig,
|
|
143
153
|
queryDefinition: () => queryDefinition,
|
|
154
|
+
queryFieldsSatisfied: () => queryFieldsSatisfied,
|
|
155
|
+
queryTablesSatisfied: () => queryTablesSatisfied,
|
|
144
156
|
resolveRecordSchema: () => resolveRecordSchema,
|
|
145
157
|
selectsWithoutValues: () => selectsWithoutValues,
|
|
146
158
|
storageConfigKeys: () => storageConfigKeys,
|
|
@@ -149,12 +161,14 @@ __export(exports_src, {
|
|
|
149
161
|
transformConnectionToEdge: () => transformConnectionToEdge,
|
|
150
162
|
transformFlowToWorkflow: () => transformFlowToWorkflow,
|
|
151
163
|
transformStepToNode: () => transformStepToNode,
|
|
164
|
+
validateBuildQueryConfig: () => validateBuildQueryConfig,
|
|
152
165
|
validateConnectorTriggerEvents: () => validateConnectorTriggerEvents,
|
|
153
166
|
validateEdge: () => validateEdge,
|
|
154
167
|
validateFlowData: () => validateFlowData,
|
|
155
168
|
validateNode: () => validateNode,
|
|
156
169
|
validateStepConfig: () => validateStepConfig,
|
|
157
170
|
validateStepConfigs: () => validateStepConfigs,
|
|
171
|
+
validateVisualConfig: () => validateVisualConfig,
|
|
158
172
|
validateWorkflow: () => validateWorkflow
|
|
159
173
|
});
|
|
160
174
|
module.exports = __toCommonJS(exports_src);
|
|
@@ -1183,32 +1197,51 @@ var QueryType;
|
|
|
1183
1197
|
QueryType["Delete"] = "delete";
|
|
1184
1198
|
QueryType["Custom"] = "custom";
|
|
1185
1199
|
})(QueryType ||= {});
|
|
1186
|
-
var
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1200
|
+
var QUERY_MODES = ["visual" /* Visual */];
|
|
1201
|
+
var QUERY_TYPES = ["select" /* Select */];
|
|
1202
|
+
var QUERY_COMPARISON_OPERATORS = [
|
|
1203
|
+
"=",
|
|
1204
|
+
"!=",
|
|
1205
|
+
">",
|
|
1206
|
+
"<",
|
|
1207
|
+
">=",
|
|
1208
|
+
"<=",
|
|
1209
|
+
"equals",
|
|
1210
|
+
"notEquals",
|
|
1211
|
+
"greaterThan",
|
|
1212
|
+
"lessThan",
|
|
1213
|
+
"greaterOrEqual",
|
|
1214
|
+
"lessOrEqual"
|
|
1215
|
+
];
|
|
1216
|
+
var QUERY_RELATIVE_DATE_OPERATORS = ["inRelative", "notInRelative"];
|
|
1217
|
+
var QUERY_CONDITION_OPERATORS = [
|
|
1218
|
+
...QUERY_COMPARISON_OPERATORS,
|
|
1219
|
+
"contains",
|
|
1220
|
+
"startsWith",
|
|
1221
|
+
"endsWith",
|
|
1222
|
+
"like",
|
|
1223
|
+
"notLike",
|
|
1224
|
+
"in",
|
|
1225
|
+
"notIn",
|
|
1226
|
+
"between",
|
|
1227
|
+
"isNull",
|
|
1228
|
+
"isNotNull",
|
|
1229
|
+
...QUERY_RELATIVE_DATE_OPERATORS
|
|
1230
|
+
];
|
|
1231
|
+
var RELATIVE_DATE_DIRECTIONS = ["last", "next", "this"];
|
|
1232
|
+
var RELATIVE_DATE_UNITS = ["day", "week", "month", "quarter", "year"];
|
|
1233
|
+
var RELATIVE_DATE_MAX_AMOUNT = 3650;
|
|
1234
|
+
var MAX_CONDITION_DEPTH = 3;
|
|
1235
|
+
var isConditionGroup = (c) => !!c && typeof c === "object" && c.type === "group";
|
|
1236
|
+
var QUERY_AGGREGATE_FUNCTIONS = ["COUNT", "COUNT_DISTINCT", "SUM", "AVG", "MIN", "MAX"];
|
|
1191
1237
|
var SortDirection;
|
|
1192
1238
|
((SortDirection) => {
|
|
1193
1239
|
SortDirection["Ascending"] = "asc";
|
|
1194
1240
|
SortDirection["Descending"] = "desc";
|
|
1195
1241
|
})(SortDirection ||= {});
|
|
1196
|
-
|
|
1197
|
-
(
|
|
1198
|
-
|
|
1199
|
-
JoinType["Left"] = "left";
|
|
1200
|
-
JoinType["Right"] = "right";
|
|
1201
|
-
JoinType["Full"] = "full";
|
|
1202
|
-
})(JoinType ||= {});
|
|
1203
|
-
var AggregateFunction;
|
|
1204
|
-
((AggregateFunction) => {
|
|
1205
|
-
AggregateFunction["Count"] = "count";
|
|
1206
|
-
AggregateFunction["Sum"] = "sum";
|
|
1207
|
-
AggregateFunction["Average"] = "avg";
|
|
1208
|
-
AggregateFunction["Min"] = "min";
|
|
1209
|
-
AggregateFunction["Max"] = "max";
|
|
1210
|
-
AggregateFunction["CountDistinct"] = "count_distinct";
|
|
1211
|
-
})(AggregateFunction ||= {});
|
|
1242
|
+
// src/catalog/isOneOf.ts
|
|
1243
|
+
var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
|
|
1244
|
+
|
|
1212
1245
|
// src/catalog/general/query/config.ts
|
|
1213
1246
|
var BuildQueryConfigDefaults = {
|
|
1214
1247
|
mode: "visual" /* Visual */,
|
|
@@ -1218,160 +1251,262 @@ var BuildQueryConfigDefaults = {
|
|
|
1218
1251
|
visualConfig: {
|
|
1219
1252
|
tables: [],
|
|
1220
1253
|
fields: [],
|
|
1221
|
-
|
|
1254
|
+
conditions: [],
|
|
1222
1255
|
joins: [],
|
|
1223
1256
|
groupBy: [],
|
|
1257
|
+
aggregates: [],
|
|
1224
1258
|
sort: [],
|
|
1225
|
-
limit: 10
|
|
1226
|
-
offset: 0
|
|
1227
|
-
},
|
|
1228
|
-
sqlConfig: {
|
|
1229
|
-
sql: "",
|
|
1230
|
-
parameters: {}
|
|
1259
|
+
limit: 10
|
|
1231
1260
|
}
|
|
1232
1261
|
};
|
|
1233
|
-
var
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1262
|
+
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.";
|
|
1263
|
+
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.";
|
|
1264
|
+
var err = (field, message) => ({ field, message, severity: "error" });
|
|
1265
|
+
var isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
1266
|
+
var nonEmptyString = (v) => typeof v === "string" && v.trim().length > 0;
|
|
1267
|
+
var hasValue = (v) => v !== undefined && v !== null && !(typeof v === "string" && v.trim() === "") && !(Array.isArray(v) && v.length === 0);
|
|
1268
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
1269
|
+
function queryFieldsSatisfied(config) {
|
|
1270
|
+
const vc = isRecord(config?.visualConfig) ? config.visualConfig : undefined;
|
|
1271
|
+
if (!vc)
|
|
1272
|
+
return false;
|
|
1273
|
+
if (vc.selectAll === true)
|
|
1274
|
+
return true;
|
|
1275
|
+
if (arr(vc.fields).length > 0)
|
|
1276
|
+
return true;
|
|
1277
|
+
if (arr(vc.aggregates).length > 0 || arr(vc.groupBy).length > 0)
|
|
1278
|
+
return true;
|
|
1279
|
+
const sql = isRecord(config?.sqlConfig) ? config.sqlConfig.sql : undefined;
|
|
1280
|
+
return arr(vc.tables).length > 0 && nonEmptyString(sql);
|
|
1281
|
+
}
|
|
1282
|
+
function queryTablesSatisfied(config) {
|
|
1283
|
+
const vc = isRecord(config?.visualConfig) ? config.visualConfig : undefined;
|
|
1284
|
+
if (!vc)
|
|
1285
|
+
return false;
|
|
1286
|
+
return vc.selectAll === true || arr(vc.tables).length > 0;
|
|
1287
|
+
}
|
|
1288
|
+
var OPERATOR_LIST = QUERY_CONDITION_OPERATORS.join(", ");
|
|
1289
|
+
function validateRelative(path, relative) {
|
|
1290
|
+
if (!isRecord(relative)) {
|
|
1291
|
+
return [err(`${path}.relative`, `${path}: a relative-date condition needs relative, e.g. { direction: 'last', amount: 30, unit: 'day' }`)];
|
|
1242
1292
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
severity: "error"
|
|
1248
|
-
});
|
|
1293
|
+
const issues = [];
|
|
1294
|
+
const { direction, unit, amount, includeCurrent } = relative;
|
|
1295
|
+
if (!isOneOf(RELATIVE_DATE_DIRECTIONS, direction)) {
|
|
1296
|
+
issues.push(err(`${path}.relative.direction`, `${path}.relative.direction must be one of: ${RELATIVE_DATE_DIRECTIONS.join(", ")} (got "${String(direction)}")`));
|
|
1249
1297
|
}
|
|
1250
|
-
if (!
|
|
1251
|
-
|
|
1252
|
-
field: "queryType",
|
|
1253
|
-
message: "Query type is required",
|
|
1254
|
-
severity: "error"
|
|
1255
|
-
});
|
|
1298
|
+
if (!isOneOf(RELATIVE_DATE_UNITS, unit)) {
|
|
1299
|
+
issues.push(err(`${path}.relative.unit`, `${path}.relative.unit must be one of: ${RELATIVE_DATE_UNITS.join(", ")} (got "${String(unit)}")`));
|
|
1256
1300
|
}
|
|
1257
|
-
if (
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
errors.push(...sqlErrors);
|
|
1301
|
+
if (direction === "last" || direction === "next") {
|
|
1302
|
+
const ok = typeof amount === "number" && Number.isInteger(amount) && amount >= 1 && amount <= RELATIVE_DATE_MAX_AMOUNT;
|
|
1303
|
+
if (!ok) {
|
|
1304
|
+
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' })`));
|
|
1305
|
+
}
|
|
1263
1306
|
}
|
|
1264
|
-
if (
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1307
|
+
if (includeCurrent !== undefined && typeof includeCurrent !== "boolean") {
|
|
1308
|
+
issues.push(err(`${path}.relative.includeCurrent`, `${path}.relative.includeCurrent must be true or false`));
|
|
1309
|
+
}
|
|
1310
|
+
return issues;
|
|
1311
|
+
}
|
|
1312
|
+
function validateRow(path, row, inHaving) {
|
|
1313
|
+
const issues = [];
|
|
1314
|
+
if (!nonEmptyString(row.field)) {
|
|
1315
|
+
issues.push(err(`${path}.field`, `${path}: choose the field to compare (field is empty)`));
|
|
1316
|
+
}
|
|
1317
|
+
if (!isOneOf(QUERY_CONDITION_OPERATORS, row.operator)) {
|
|
1318
|
+
issues.push(err(`${path}.operator`, `${path}.operator must be one of: ${OPERATOR_LIST} (got "${String(row.operator)}")`));
|
|
1319
|
+
return issues;
|
|
1320
|
+
}
|
|
1321
|
+
const op = row.operator;
|
|
1322
|
+
const isRelative = isOneOf(QUERY_RELATIVE_DATE_OPERATORS, op);
|
|
1323
|
+
const comparesField = row.valueField !== undefined && row.valueField !== null && row.valueField !== "";
|
|
1324
|
+
if (isRelative) {
|
|
1325
|
+
if (inHaving) {
|
|
1326
|
+
issues.push(err(`${path}.operator`, `${path}: '${op}' filters dates on rows — put it in conditions, not having`));
|
|
1327
|
+
} else {
|
|
1328
|
+
issues.push(...validateRelative(path, row.relative));
|
|
1329
|
+
}
|
|
1330
|
+
if (hasValue(row.value) || comparesField) {
|
|
1331
|
+
issues.push(err(path, `${path}: a relative-date condition takes its window from relative — remove value and valueField`));
|
|
1277
1332
|
}
|
|
1333
|
+
} else if (row.relative !== undefined) {
|
|
1334
|
+
issues.push(err(`${path}.relative`, `${path}: relative is only used with 'inRelative' or 'notInRelative' — change the operator or remove relative`));
|
|
1278
1335
|
}
|
|
1279
|
-
if (
|
|
1280
|
-
if (
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
message: "Max rows is very high (>10,000). This may impact performance.",
|
|
1290
|
-
severity: "warning"
|
|
1291
|
-
});
|
|
1336
|
+
if (comparesField) {
|
|
1337
|
+
if (inHaving) {
|
|
1338
|
+
issues.push(err(`${path}.valueField`, `${path}: having compares with a value — use value, not valueField`));
|
|
1339
|
+
} else if (!nonEmptyString(row.valueField)) {
|
|
1340
|
+
issues.push(err(`${path}.valueField`, `${path}.valueField must name the field to compare with`));
|
|
1341
|
+
} else if (!isOneOf(QUERY_COMPARISON_OPERATORS, op)) {
|
|
1342
|
+
issues.push(err(`${path}.valueField`, `${path}: comparing with another field works with =, !=, >, <, >=, <= only (got "${op}")`));
|
|
1343
|
+
}
|
|
1344
|
+
if (hasValue(row.value)) {
|
|
1345
|
+
issues.push(err(path, `${path}: set either value (compare with a value) or valueField (compare with another field), not both`));
|
|
1292
1346
|
}
|
|
1293
1347
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1348
|
+
if (row.joinOperator !== undefined && !(typeof row.joinOperator === "string" && /^(and|or)$/i.test(row.joinOperator))) {
|
|
1349
|
+
issues.push(err(`${path}.joinOperator`, `${path}.joinOperator must be AND or OR (got "${String(row.joinOperator)}")`));
|
|
1350
|
+
}
|
|
1351
|
+
return issues;
|
|
1352
|
+
}
|
|
1353
|
+
function validateConditionList(path, list, depth) {
|
|
1354
|
+
if (list === undefined || list === null)
|
|
1355
|
+
return [];
|
|
1356
|
+
if (!Array.isArray(list))
|
|
1357
|
+
return [err(path, `${path} must be a list of conditions`)];
|
|
1358
|
+
const issues = [];
|
|
1359
|
+
list.forEach((item, i) => {
|
|
1360
|
+
const at = `${path}[${i}]`;
|
|
1361
|
+
if (!isRecord(item)) {
|
|
1362
|
+
issues.push(err(at, `${at} must be a condition ({ field, operator, value }) or a group ({ type: 'group', conditions })`));
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (isConditionGroup(item)) {
|
|
1366
|
+
if (depth + 1 > MAX_CONDITION_DEPTH) {
|
|
1367
|
+
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`));
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (item.joinOperator !== undefined && !(typeof item.joinOperator === "string" && /^(and|or)$/i.test(item.joinOperator))) {
|
|
1371
|
+
issues.push(err(`${at}.joinOperator`, `${at}.joinOperator must be AND or OR (got "${String(item.joinOperator)}")`));
|
|
1372
|
+
}
|
|
1373
|
+
if (!Array.isArray(item.conditions) || item.conditions.length === 0) {
|
|
1374
|
+
issues.push(err(`${at}.conditions`, `${at}: a group needs at least one condition — add one or remove the group`));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
issues.push(...validateConditionList(`${at}.conditions`, item.conditions, depth + 1));
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
issues.push(...validateRow(at, item, false));
|
|
1381
|
+
});
|
|
1382
|
+
return issues;
|
|
1383
|
+
}
|
|
1384
|
+
function validateAggregates(vc) {
|
|
1385
|
+
const list = vc.aggregates;
|
|
1386
|
+
if (list === undefined || list === null)
|
|
1387
|
+
return [];
|
|
1388
|
+
if (!Array.isArray(list))
|
|
1389
|
+
return [err("visualConfig.aggregates", "visualConfig.aggregates must be a list of { fn, field, alias }")];
|
|
1390
|
+
const issues = [];
|
|
1391
|
+
list.forEach((a, i) => {
|
|
1392
|
+
const at = `visualConfig.aggregates[${i}]`;
|
|
1393
|
+
if (!isRecord(a)) {
|
|
1394
|
+
issues.push(err(at, `${at} must be { fn, field, alias }`));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const fn = typeof a.fn === "string" ? a.fn.toUpperCase() : a.fn;
|
|
1398
|
+
if (!isOneOf(QUERY_AGGREGATE_FUNCTIONS, fn)) {
|
|
1399
|
+
issues.push(err(`${at}.fn`, `${at}.fn must be one of: ${QUERY_AGGREGATE_FUNCTIONS.join(", ")} (got "${String(a.fn)}")`));
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const allRows = !nonEmptyString(a.field) || a.field.trim() === "*";
|
|
1403
|
+
if (allRows && fn !== "COUNT") {
|
|
1404
|
+
issues.push(err(`${at}.field`, `${at}: ${fn} needs a field to work on — set field to a column name`));
|
|
1405
|
+
}
|
|
1406
|
+
if (a.alias !== undefined && a.alias !== null && !nonEmptyString(a.alias)) {
|
|
1407
|
+
issues.push(err(`${at}.alias`, `${at}.alias must be a name for the result column, or left out`));
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
return issues;
|
|
1411
|
+
}
|
|
1412
|
+
function validateHaving(vc) {
|
|
1413
|
+
const list = vc.having;
|
|
1414
|
+
if (list === undefined || list === null || Array.isArray(list) && list.length === 0)
|
|
1415
|
+
return [];
|
|
1416
|
+
if (!Array.isArray(list))
|
|
1417
|
+
return [err("visualConfig.having", "visualConfig.having must be a list of conditions")];
|
|
1418
|
+
const aggregates = arr(vc.aggregates).filter(isRecord);
|
|
1419
|
+
const groupBy = arr(vc.groupBy).filter(nonEmptyString);
|
|
1420
|
+
if (aggregates.length === 0 && groupBy.length === 0) {
|
|
1421
|
+
return [err("visualConfig.having", "visualConfig.having filters grouped results, so it needs aggregates or groupBy — add them, or move these filters to conditions")];
|
|
1422
|
+
}
|
|
1423
|
+
const names = new Set([
|
|
1424
|
+
...groupBy,
|
|
1425
|
+
...aggregates.map((a) => a.alias).filter(nonEmptyString)
|
|
1426
|
+
]);
|
|
1427
|
+
const issues = [];
|
|
1428
|
+
list.forEach((row, i) => {
|
|
1429
|
+
const at = `visualConfig.having[${i}]`;
|
|
1430
|
+
if (!isRecord(row) || isConditionGroup(row)) {
|
|
1431
|
+
issues.push(err(at, `${at} must be a single condition ({ field, operator, value }); groups are not used in having`));
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
issues.push(...validateRow(at, row, true));
|
|
1435
|
+
if (nonEmptyString(row.field) && !names.has(row.field)) {
|
|
1436
|
+
const known = [...names];
|
|
1437
|
+
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)")));
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
return issues;
|
|
1441
|
+
}
|
|
1442
|
+
function validateVisualConfig(config) {
|
|
1443
|
+
const vc = config.visualConfig;
|
|
1444
|
+
if (!isRecord(vc)) {
|
|
1445
|
+
return [err("visualConfig", "visualConfig is required — describe the query with tables (what to read), fields (which columns) and optional conditions")];
|
|
1446
|
+
}
|
|
1447
|
+
const issues = [];
|
|
1448
|
+
if (!queryTablesSatisfied(config)) {
|
|
1449
|
+
issues.push(err("visualConfig.tables", "At least one table must be selected (visualConfig.tables)"));
|
|
1450
|
+
}
|
|
1451
|
+
if (!queryFieldsSatisfied(config)) {
|
|
1452
|
+
issues.push(err("visualConfig.fields", "At least one field must be selected (visualConfig.fields), or add aggregates / groupBy for a summary query"));
|
|
1453
|
+
}
|
|
1454
|
+
if (arr(vc.tables).length > 1 && arr(vc.joins).length === 0) {
|
|
1455
|
+
issues.push(err("visualConfig.joins", "Joins are required when selecting from multiple tables (visualConfig.joins)"));
|
|
1456
|
+
}
|
|
1457
|
+
if (vc.distinct !== undefined && typeof vc.distinct !== "boolean") {
|
|
1458
|
+
issues.push(err("visualConfig.distinct", "visualConfig.distinct must be true or false"));
|
|
1459
|
+
}
|
|
1460
|
+
issues.push(...validateConditionList("visualConfig.conditions", vc.conditions, 0));
|
|
1461
|
+
issues.push(...validateAggregates(vc));
|
|
1462
|
+
issues.push(...validateHaving(vc));
|
|
1463
|
+
return issues;
|
|
1464
|
+
}
|
|
1465
|
+
var validateBuildQueryConfig = (config) => {
|
|
1466
|
+
const c = config;
|
|
1301
1467
|
const errors = [];
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
message: "Visual query configuration is required",
|
|
1306
|
-
severity: "error"
|
|
1307
|
-
});
|
|
1308
|
-
return errors;
|
|
1468
|
+
const warnings = [];
|
|
1469
|
+
if (!c.connectorId) {
|
|
1470
|
+
errors.push(err("connectorId", "Please select a connector (connectorId)"));
|
|
1309
1471
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1472
|
+
const mode = c.mode;
|
|
1473
|
+
const visual = mode === undefined || mode === null || mode === "" || isOneOf(QUERY_MODES, mode);
|
|
1474
|
+
if (!visual) {
|
|
1475
|
+
errors.push(err("mode", `Mode must be one of: ${QUERY_MODES.join(", ")} (got "${String(mode)}"). ${QUERY_VISUAL_ONLY_MESSAGE}`));
|
|
1476
|
+
} else if (!mode) {
|
|
1477
|
+
errors.push(err("mode", "Query mode is required — set mode to 'visual'"));
|
|
1316
1478
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
});
|
|
1479
|
+
const queryType = c.queryType;
|
|
1480
|
+
if (!queryType) {
|
|
1481
|
+
errors.push(err("queryType", "Query type is required — set queryType to 'select'"));
|
|
1482
|
+
} else if (!isOneOf(QUERY_TYPES, queryType)) {
|
|
1483
|
+
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."));
|
|
1323
1484
|
}
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
});
|
|
1485
|
+
if (visual) {
|
|
1486
|
+
const sql = isRecord(c.sqlConfig) ? c.sqlConfig.sql : undefined;
|
|
1487
|
+
if (nonEmptyString(sql) && !isRecord(c.visualConfig)) {
|
|
1488
|
+
errors.push(err("sqlConfig.sql", QUERY_AUTHORED_SQL_MESSAGE));
|
|
1489
|
+
} else {
|
|
1490
|
+
errors.push(...validateVisualConfig(c));
|
|
1331
1491
|
}
|
|
1332
1492
|
}
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
message: "Group by is required when mixing aggregate and non-aggregate fields",
|
|
1340
|
-
severity: "error"
|
|
1341
|
-
});
|
|
1493
|
+
const timeout = c.timeout;
|
|
1494
|
+
if (typeof timeout === "number") {
|
|
1495
|
+
if (timeout < 1000) {
|
|
1496
|
+
errors.push(err("timeout", "Timeout must be at least 1000ms"));
|
|
1497
|
+
} else if (timeout > 300000) {
|
|
1498
|
+
warnings.push({ field: "timeout", message: "Timeout is very high (>5 minutes)", severity: "warning" });
|
|
1342
1499
|
}
|
|
1343
1500
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
field: "sqlConfig",
|
|
1351
|
-
message: "SQL query configuration is required",
|
|
1352
|
-
severity: "error"
|
|
1353
|
-
});
|
|
1354
|
-
return errors;
|
|
1355
|
-
}
|
|
1356
|
-
if (!config.sql || config.sql.trim().length === 0) {
|
|
1357
|
-
errors.push({
|
|
1358
|
-
field: "sqlConfig.sql",
|
|
1359
|
-
message: "SQL query is required",
|
|
1360
|
-
severity: "error"
|
|
1361
|
-
});
|
|
1362
|
-
} else {
|
|
1363
|
-
const sql = config.sql.trim().toLowerCase();
|
|
1364
|
-
const dangerousKeywords = ["drop", "truncate", "alter", "create", "grant", "revoke"];
|
|
1365
|
-
const hasDangerousKeyword = dangerousKeywords.some((keyword) => sql.includes(keyword + " "));
|
|
1366
|
-
if (hasDangerousKeyword) {
|
|
1367
|
-
errors.push({
|
|
1368
|
-
field: "sqlConfig.sql",
|
|
1369
|
-
message: "SQL query contains potentially dangerous operations (DROP, TRUNCATE, ALTER, etc.)",
|
|
1370
|
-
severity: "error"
|
|
1371
|
-
});
|
|
1501
|
+
const maxRows = c.maxRows;
|
|
1502
|
+
if (typeof maxRows === "number") {
|
|
1503
|
+
if (maxRows < 1) {
|
|
1504
|
+
errors.push(err("maxRows", "Max rows must be at least 1"));
|
|
1505
|
+
} else if (maxRows > 1e4) {
|
|
1506
|
+
warnings.push({ field: "maxRows", message: "Max rows is very high (>10,000). This may impact performance.", severity: "warning" });
|
|
1372
1507
|
}
|
|
1373
1508
|
}
|
|
1374
|
-
return errors;
|
|
1509
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
1375
1510
|
};
|
|
1376
1511
|
function isQueryableConnector(connector) {
|
|
1377
1512
|
if (connector.isQueryable !== undefined) {
|
|
@@ -1403,17 +1538,26 @@ var BuildQueryContractSchema = {
|
|
|
1403
1538
|
mode: {
|
|
1404
1539
|
type: "select",
|
|
1405
1540
|
required: true,
|
|
1406
|
-
options:
|
|
1407
|
-
description: "visual: the
|
|
1541
|
+
options: QUERY_MODES,
|
|
1542
|
+
description: "Always 'visual': the Query step is built with the visual builder and described in visualConfig. Hand-written SQL is not accepted."
|
|
1408
1543
|
},
|
|
1409
1544
|
queryType: {
|
|
1410
1545
|
type: "select",
|
|
1411
1546
|
required: true,
|
|
1412
|
-
options:
|
|
1413
|
-
description: "
|
|
1547
|
+
options: QUERY_TYPES,
|
|
1548
|
+
description: "Always 'select': the Query step reads data. Use a Transform step to write."
|
|
1549
|
+
},
|
|
1550
|
+
visualConfig: {
|
|
1551
|
+
type: "object",
|
|
1552
|
+
required: false,
|
|
1553
|
+
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)."
|
|
1554
|
+
},
|
|
1555
|
+
sqlConfig: {
|
|
1556
|
+
type: "object",
|
|
1557
|
+
required: false,
|
|
1558
|
+
derived: true,
|
|
1559
|
+
description: "Compiled from visualConfig by the editor or the server. Never write it — describe the query in visualConfig."
|
|
1414
1560
|
},
|
|
1415
|
-
visualConfig: { type: "object", required: false },
|
|
1416
|
-
sqlConfig: { type: "object", required: false },
|
|
1417
1561
|
timeout: { type: "number", required: false },
|
|
1418
1562
|
maxRows: { type: "number", required: false }
|
|
1419
1563
|
};
|
|
@@ -1422,11 +1566,11 @@ var BuildQueryContractSchema = {
|
|
|
1422
1566
|
var queryDefinition = {
|
|
1423
1567
|
id: "query",
|
|
1424
1568
|
name: "Query",
|
|
1425
|
-
description: "
|
|
1426
|
-
longDescription: "
|
|
1569
|
+
description: "Read records from a connected database, app or CRM",
|
|
1570
|
+
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.",
|
|
1427
1571
|
category: "database" /* Database */,
|
|
1428
1572
|
type: "query" /* Query */,
|
|
1429
|
-
tags: ["query", "database", "
|
|
1573
|
+
tags: ["query", "database", "visual-builder", "no-code", "filter", "api", "crm", "built-in"],
|
|
1430
1574
|
status: "stable" /* Stable */,
|
|
1431
1575
|
icon: "QueryQueue",
|
|
1432
1576
|
color: "#0043ce",
|
|
@@ -1444,17 +1588,12 @@ var queryDefinition = {
|
|
|
1444
1588
|
key: "mode",
|
|
1445
1589
|
label: "Query Mode",
|
|
1446
1590
|
type: "select" /* Select */,
|
|
1447
|
-
description: "
|
|
1591
|
+
description: "Always 'visual' — the query is built with the visual builder",
|
|
1448
1592
|
options: [
|
|
1449
1593
|
{
|
|
1450
|
-
label: "Visual Builder
|
|
1594
|
+
label: "Visual Builder",
|
|
1451
1595
|
value: "visual" /* Visual */,
|
|
1452
|
-
description: "
|
|
1453
|
-
},
|
|
1454
|
-
{
|
|
1455
|
-
label: "SQL Editor (Advanced)",
|
|
1456
|
-
value: "sql" /* SQL */,
|
|
1457
|
-
description: "Write raw SQL queries with syntax highlighting"
|
|
1596
|
+
description: "Pick the table, fields and filters — no code needed"
|
|
1458
1597
|
}
|
|
1459
1598
|
],
|
|
1460
1599
|
defaultValue: "visual" /* Visual */,
|
|
@@ -1502,7 +1641,7 @@ var queryDefinition = {
|
|
|
1502
1641
|
key: "visualConfig.fields",
|
|
1503
1642
|
label: "Select Fields",
|
|
1504
1643
|
type: "array" /* Array */,
|
|
1505
|
-
description:
|
|
1644
|
+
description: 'The fields to return, as "table.column". Not needed for a summary query (aggregates / groupBy) or when every table and field is selected.',
|
|
1506
1645
|
validation: { required: true },
|
|
1507
1646
|
conditional: [
|
|
1508
1647
|
{
|
|
@@ -1545,39 +1684,6 @@ var queryDefinition = {
|
|
|
1545
1684
|
}
|
|
1546
1685
|
]
|
|
1547
1686
|
},
|
|
1548
|
-
{
|
|
1549
|
-
key: "sqlConfig.sql",
|
|
1550
|
-
label: "SQL Query",
|
|
1551
|
-
type: "code" /* Code */,
|
|
1552
|
-
language: "sql",
|
|
1553
|
-
description: "Write your SQL query here",
|
|
1554
|
-
placeholder: "SELECT * FROM users WHERE status = 'active' LIMIT 100",
|
|
1555
|
-
validation: { required: true },
|
|
1556
|
-
conditional: [
|
|
1557
|
-
{
|
|
1558
|
-
field: "mode",
|
|
1559
|
-
operator: "==",
|
|
1560
|
-
value: "sql" /* SQL */,
|
|
1561
|
-
action: "show"
|
|
1562
|
-
}
|
|
1563
|
-
]
|
|
1564
|
-
},
|
|
1565
|
-
{
|
|
1566
|
-
key: "sqlConfig.parameters",
|
|
1567
|
-
label: "Query Parameters",
|
|
1568
|
-
type: "json" /* JSON */,
|
|
1569
|
-
description: 'Parameters for parameterized queries (e.g., {"userId": 123})',
|
|
1570
|
-
validation: { required: false },
|
|
1571
|
-
advanced: true,
|
|
1572
|
-
conditional: [
|
|
1573
|
-
{
|
|
1574
|
-
field: "mode",
|
|
1575
|
-
operator: "==",
|
|
1576
|
-
value: "sql" /* SQL */,
|
|
1577
|
-
action: "show"
|
|
1578
|
-
}
|
|
1579
|
-
]
|
|
1580
|
-
},
|
|
1581
1687
|
{
|
|
1582
1688
|
key: "timeout",
|
|
1583
1689
|
label: "Query Timeout (ms)",
|
|
@@ -1591,7 +1697,7 @@ var queryDefinition = {
|
|
|
1591
1697
|
key: "maxRows",
|
|
1592
1698
|
label: "Max Rows",
|
|
1593
1699
|
type: "number" /* Number */,
|
|
1594
|
-
description: "Legacy step-level field —
|
|
1700
|
+
description: "Legacy step-level field — not read at run. To cap a run, set visualConfig.maxRows.",
|
|
1595
1701
|
defaultValue: 1000,
|
|
1596
1702
|
validation: { required: false, min: 1, max: 1e4 },
|
|
1597
1703
|
advanced: true
|
|
@@ -1607,7 +1713,7 @@ var queryDefinition = {
|
|
|
1607
1713
|
{
|
|
1608
1714
|
id: "query_mode",
|
|
1609
1715
|
label: "Query Configuration",
|
|
1610
|
-
description: "
|
|
1716
|
+
description: "The query is built with the visual builder",
|
|
1611
1717
|
fields: ["mode", "queryType"]
|
|
1612
1718
|
},
|
|
1613
1719
|
{
|
|
@@ -1616,12 +1722,6 @@ var queryDefinition = {
|
|
|
1616
1722
|
description: "Build your query visually without writing code",
|
|
1617
1723
|
fields: ["visualConfig.tables", "visualConfig.fields", "visualConfig.limit"]
|
|
1618
1724
|
},
|
|
1619
|
-
{
|
|
1620
|
-
id: "sql_editor",
|
|
1621
|
-
label: "SQL Editor",
|
|
1622
|
-
description: "Write raw SQL queries",
|
|
1623
|
-
fields: ["sqlConfig.sql", "sqlConfig.parameters"]
|
|
1624
|
-
},
|
|
1625
1725
|
{
|
|
1626
1726
|
id: "settings",
|
|
1627
1727
|
label: "Advanced Settings",
|
|
@@ -1661,7 +1761,7 @@ var queryDefinition = {
|
|
|
1661
1761
|
{
|
|
1662
1762
|
key: "generatedSQL",
|
|
1663
1763
|
type: "string" /* String */,
|
|
1664
|
-
description: "The
|
|
1764
|
+
description: "The query that ran, compiled from the visual builder",
|
|
1665
1765
|
example: "SELECT id, name, email FROM users WHERE status = 'active' LIMIT 100"
|
|
1666
1766
|
}
|
|
1667
1767
|
],
|
|
@@ -2856,6 +2956,10 @@ function isSatisfiedOutOfBand(actionId, fieldKey, config) {
|
|
|
2856
2956
|
return true;
|
|
2857
2957
|
return Array.isArray(config.blocks) && config.blocks.length > 0;
|
|
2858
2958
|
}
|
|
2959
|
+
if (actionId === "query" && fieldKey === "visualConfig.fields")
|
|
2960
|
+
return queryFieldsSatisfied(config);
|
|
2961
|
+
if (actionId === "query" && fieldKey === "visualConfig.tables")
|
|
2962
|
+
return queryTablesSatisfied(config);
|
|
2859
2963
|
return false;
|
|
2860
2964
|
}
|
|
2861
2965
|
var MESSAGE_BODY_ACTIONS = new Set([
|
|
@@ -3750,9 +3854,6 @@ var EmailConfig = {
|
|
|
3750
3854
|
}
|
|
3751
3855
|
}
|
|
3752
3856
|
};
|
|
3753
|
-
// src/catalog/isOneOf.ts
|
|
3754
|
-
var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
|
|
3755
|
-
|
|
3756
3857
|
// src/catalog/general/transform/config.ts
|
|
3757
3858
|
var TRANSFORM_MODES = ["visual", "expression"];
|
|
3758
3859
|
var TRANSFORM_OPERATIONS = [
|