qingflow-mcp 0.3.2 → 0.3.4
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/README.md +4 -2
- package/dist/server.js +341 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ Optional:
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
export QINGFLOW_FORM_CACHE_TTL_MS=300000
|
|
40
|
+
export QINGFLOW_REQUEST_TIMEOUT_MS=18000
|
|
41
|
+
export QINGFLOW_EXECUTION_BUDGET_MS=20000
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
## Run
|
|
@@ -247,8 +249,8 @@ Aggregate example (`qf_records_aggregate`):
|
|
|
247
249
|
"app_key": "your_app_key",
|
|
248
250
|
"group_by": ["归属部门", "归属销售"],
|
|
249
251
|
"amount_column": "报价总金额",
|
|
250
|
-
"requested_pages":
|
|
251
|
-
"scan_max_pages":
|
|
252
|
+
"requested_pages": 10,
|
|
253
|
+
"scan_max_pages": 10,
|
|
252
254
|
"strict_full": true
|
|
253
255
|
}
|
|
254
256
|
```
|
package/dist/server.js
CHANGED
|
@@ -44,9 +44,11 @@ class InputValidationError extends Error {
|
|
|
44
44
|
const FORM_CACHE_TTL_MS = Number(process.env.QINGFLOW_FORM_CACHE_TTL_MS ?? "300000");
|
|
45
45
|
const formCache = new Map();
|
|
46
46
|
const DEFAULT_PAGE_SIZE = 50;
|
|
47
|
-
const DEFAULT_SCAN_MAX_PAGES =
|
|
47
|
+
const DEFAULT_SCAN_MAX_PAGES = 10;
|
|
48
48
|
const DEFAULT_ROW_LIMIT = 200;
|
|
49
49
|
const MAX_LIST_ITEMS_BYTES = toPositiveInt(process.env.QINGFLOW_LIST_MAX_ITEMS_BYTES) ?? 400000;
|
|
50
|
+
const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS) ?? 18000;
|
|
51
|
+
const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
|
|
50
52
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
51
53
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
52
54
|
if (!accessToken) {
|
|
@@ -57,11 +59,12 @@ if (!baseUrl) {
|
|
|
57
59
|
}
|
|
58
60
|
const client = new QingflowClient({
|
|
59
61
|
accessToken,
|
|
60
|
-
baseUrl
|
|
62
|
+
baseUrl,
|
|
63
|
+
timeoutMs: REQUEST_TIMEOUT_MS
|
|
61
64
|
});
|
|
62
65
|
const server = new McpServer({
|
|
63
66
|
name: "qingflow-mcp",
|
|
64
|
-
version: "0.3.
|
|
67
|
+
version: "0.3.4"
|
|
65
68
|
});
|
|
66
69
|
const jsonPrimitiveSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
67
70
|
const answerValueSchema = z.union([
|
|
@@ -1225,92 +1228,149 @@ function missingRequiredFieldError(params) {
|
|
|
1225
1228
|
}
|
|
1226
1229
|
});
|
|
1227
1230
|
}
|
|
1231
|
+
const COMMON_INPUT_ALIASES = {
|
|
1232
|
+
appKey: "app_key",
|
|
1233
|
+
userId: "user_id",
|
|
1234
|
+
pageNum: "page_num",
|
|
1235
|
+
pageSize: "page_size",
|
|
1236
|
+
pageToken: "page_token",
|
|
1237
|
+
requestedPages: "requested_pages",
|
|
1238
|
+
scanMaxPages: "scan_max_pages",
|
|
1239
|
+
queryMode: "query_mode",
|
|
1240
|
+
queryLogic: "query_logic",
|
|
1241
|
+
applyId: "apply_id",
|
|
1242
|
+
applyIds: "apply_ids",
|
|
1243
|
+
maxRows: "max_rows",
|
|
1244
|
+
maxItems: "max_items",
|
|
1245
|
+
maxColumns: "max_columns",
|
|
1246
|
+
selectColumns: "select_columns",
|
|
1247
|
+
keepColumns: "keep_columns",
|
|
1248
|
+
keep_columns: "select_columns",
|
|
1249
|
+
includeAnswers: "include_answers",
|
|
1250
|
+
amountColumn: "amount_column",
|
|
1251
|
+
amountColumns: "amount_column",
|
|
1252
|
+
amount_columns: "amount_column",
|
|
1253
|
+
timeRange: "time_range",
|
|
1254
|
+
statPolicy: "stat_policy",
|
|
1255
|
+
groupBy: "group_by",
|
|
1256
|
+
strictFull: "strict_full",
|
|
1257
|
+
forceRefresh: "force_refresh",
|
|
1258
|
+
forceRefreshForm: "force_refresh_form",
|
|
1259
|
+
applyUser: "apply_user"
|
|
1260
|
+
};
|
|
1261
|
+
function applyAliases(obj, aliases) {
|
|
1262
|
+
const out = { ...obj };
|
|
1263
|
+
for (const [alias, canonical] of Object.entries(aliases)) {
|
|
1264
|
+
if (out[canonical] === undefined && out[alias] !== undefined) {
|
|
1265
|
+
out[canonical] = out[alias];
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return out;
|
|
1269
|
+
}
|
|
1228
1270
|
function normalizeListInput(raw) {
|
|
1229
|
-
const
|
|
1271
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
1272
|
+
const obj = asObject(parsedRoot);
|
|
1230
1273
|
if (!obj) {
|
|
1231
|
-
return
|
|
1274
|
+
return parsedRoot;
|
|
1232
1275
|
}
|
|
1276
|
+
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
1277
|
+
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
1233
1278
|
return {
|
|
1234
|
-
...
|
|
1235
|
-
page_num: coerceNumberLike(
|
|
1236
|
-
page_size: coerceNumberLike(
|
|
1237
|
-
requested_pages: coerceNumberLike(
|
|
1238
|
-
scan_max_pages: coerceNumberLike(
|
|
1239
|
-
type: coerceNumberLike(
|
|
1240
|
-
max_rows: coerceNumberLike(
|
|
1241
|
-
max_items: coerceNumberLike(
|
|
1242
|
-
max_columns: coerceNumberLike(
|
|
1243
|
-
strict_full: coerceBooleanLike(
|
|
1244
|
-
include_answers: coerceBooleanLike(
|
|
1245
|
-
apply_ids: normalizeIdArrayInput(
|
|
1246
|
-
sort: normalizeSortInput(
|
|
1247
|
-
filters: normalizeFiltersInput(
|
|
1248
|
-
select_columns: normalizeSelectorListInput(
|
|
1249
|
-
time_range: normalizeTimeRangeInput(
|
|
1279
|
+
...normalizedObj,
|
|
1280
|
+
page_num: coerceNumberLike(normalizedObj.page_num),
|
|
1281
|
+
page_size: coerceNumberLike(normalizedObj.page_size),
|
|
1282
|
+
requested_pages: coerceNumberLike(normalizedObj.requested_pages),
|
|
1283
|
+
scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
|
|
1284
|
+
type: coerceNumberLike(normalizedObj.type),
|
|
1285
|
+
max_rows: coerceNumberLike(normalizedObj.max_rows),
|
|
1286
|
+
max_items: coerceNumberLike(normalizedObj.max_items),
|
|
1287
|
+
max_columns: coerceNumberLike(normalizedObj.max_columns),
|
|
1288
|
+
strict_full: coerceBooleanLike(normalizedObj.strict_full),
|
|
1289
|
+
include_answers: coerceBooleanLike(normalizedObj.include_answers),
|
|
1290
|
+
apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
|
|
1291
|
+
sort: normalizeSortInput(normalizedObj.sort),
|
|
1292
|
+
filters: normalizeFiltersInput(normalizedObj.filters),
|
|
1293
|
+
select_columns: normalizeSelectorListInput(selectColumns),
|
|
1294
|
+
time_range: normalizeTimeRangeInput(normalizedObj.time_range)
|
|
1250
1295
|
};
|
|
1251
1296
|
}
|
|
1252
1297
|
function normalizeRecordGetInput(raw) {
|
|
1253
|
-
const
|
|
1298
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
1299
|
+
const obj = asObject(parsedRoot);
|
|
1254
1300
|
if (!obj) {
|
|
1255
|
-
return
|
|
1301
|
+
return parsedRoot;
|
|
1256
1302
|
}
|
|
1303
|
+
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
1304
|
+
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
1257
1305
|
return {
|
|
1258
|
-
...
|
|
1259
|
-
apply_id: coerceNumberLike(
|
|
1260
|
-
max_columns: coerceNumberLike(
|
|
1261
|
-
select_columns: normalizeSelectorListInput(
|
|
1306
|
+
...normalizedObj,
|
|
1307
|
+
apply_id: coerceNumberLike(normalizedObj.apply_id),
|
|
1308
|
+
max_columns: coerceNumberLike(normalizedObj.max_columns),
|
|
1309
|
+
select_columns: normalizeSelectorListInput(selectColumns)
|
|
1262
1310
|
};
|
|
1263
1311
|
}
|
|
1264
1312
|
function normalizeQueryInput(raw) {
|
|
1265
|
-
const
|
|
1313
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
1314
|
+
const obj = asObject(parsedRoot);
|
|
1266
1315
|
if (!obj) {
|
|
1267
|
-
return
|
|
1316
|
+
return parsedRoot;
|
|
1268
1317
|
}
|
|
1318
|
+
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
1319
|
+
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
1269
1320
|
return {
|
|
1270
|
-
...
|
|
1271
|
-
page_num: coerceNumberLike(
|
|
1272
|
-
page_size: coerceNumberLike(
|
|
1273
|
-
requested_pages: coerceNumberLike(
|
|
1274
|
-
scan_max_pages: coerceNumberLike(
|
|
1275
|
-
type: coerceNumberLike(
|
|
1276
|
-
max_rows: coerceNumberLike(
|
|
1277
|
-
max_items: coerceNumberLike(
|
|
1278
|
-
max_columns: coerceNumberLike(
|
|
1279
|
-
apply_id: coerceNumberLike(
|
|
1280
|
-
strict_full: coerceBooleanLike(
|
|
1281
|
-
include_answers: coerceBooleanLike(
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1321
|
+
...normalizedObj,
|
|
1322
|
+
page_num: coerceNumberLike(normalizedObj.page_num),
|
|
1323
|
+
page_size: coerceNumberLike(normalizedObj.page_size),
|
|
1324
|
+
requested_pages: coerceNumberLike(normalizedObj.requested_pages),
|
|
1325
|
+
scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
|
|
1326
|
+
type: coerceNumberLike(normalizedObj.type),
|
|
1327
|
+
max_rows: coerceNumberLike(normalizedObj.max_rows),
|
|
1328
|
+
max_items: coerceNumberLike(normalizedObj.max_items),
|
|
1329
|
+
max_columns: coerceNumberLike(normalizedObj.max_columns),
|
|
1330
|
+
apply_id: coerceNumberLike(normalizedObj.apply_id),
|
|
1331
|
+
strict_full: coerceBooleanLike(normalizedObj.strict_full),
|
|
1332
|
+
include_answers: coerceBooleanLike(normalizedObj.include_answers),
|
|
1333
|
+
amount_column: coerceNumberLike(normalizedObj.amount_column),
|
|
1334
|
+
apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
|
|
1335
|
+
sort: normalizeSortInput(normalizedObj.sort),
|
|
1336
|
+
filters: normalizeFiltersInput(normalizedObj.filters),
|
|
1337
|
+
select_columns: normalizeSelectorListInput(selectColumns),
|
|
1338
|
+
time_range: normalizeTimeRangeInput(normalizedObj.time_range),
|
|
1339
|
+
stat_policy: normalizeStatPolicyInput(normalizedObj.stat_policy)
|
|
1287
1340
|
};
|
|
1288
1341
|
}
|
|
1289
1342
|
function normalizeAggregateInput(raw) {
|
|
1290
|
-
const
|
|
1343
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
1344
|
+
const obj = asObject(parsedRoot);
|
|
1291
1345
|
if (!obj) {
|
|
1292
|
-
return
|
|
1346
|
+
return parsedRoot;
|
|
1293
1347
|
}
|
|
1348
|
+
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
1294
1349
|
return {
|
|
1295
|
-
...
|
|
1296
|
-
page_num: coerceNumberLike(
|
|
1297
|
-
page_size: coerceNumberLike(
|
|
1298
|
-
requested_pages: coerceNumberLike(
|
|
1299
|
-
scan_max_pages: coerceNumberLike(
|
|
1300
|
-
type: coerceNumberLike(
|
|
1301
|
-
max_groups: coerceNumberLike(
|
|
1302
|
-
strict_full: coerceBooleanLike(
|
|
1303
|
-
group_by: normalizeSelectorListInput(
|
|
1304
|
-
amount_column: coerceNumberLike(
|
|
1305
|
-
apply_ids: normalizeIdArrayInput(
|
|
1306
|
-
sort: normalizeSortInput(
|
|
1307
|
-
filters: normalizeFiltersInput(
|
|
1308
|
-
time_range: normalizeTimeRangeInput(
|
|
1350
|
+
...normalizedObj,
|
|
1351
|
+
page_num: coerceNumberLike(normalizedObj.page_num),
|
|
1352
|
+
page_size: coerceNumberLike(normalizedObj.page_size),
|
|
1353
|
+
requested_pages: coerceNumberLike(normalizedObj.requested_pages),
|
|
1354
|
+
scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
|
|
1355
|
+
type: coerceNumberLike(normalizedObj.type),
|
|
1356
|
+
max_groups: coerceNumberLike(normalizedObj.max_groups),
|
|
1357
|
+
strict_full: coerceBooleanLike(normalizedObj.strict_full),
|
|
1358
|
+
group_by: normalizeSelectorListInput(normalizedObj.group_by),
|
|
1359
|
+
amount_column: coerceNumberLike(normalizedObj.amount_column),
|
|
1360
|
+
apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
|
|
1361
|
+
sort: normalizeSortInput(normalizedObj.sort),
|
|
1362
|
+
filters: normalizeFiltersInput(normalizedObj.filters),
|
|
1363
|
+
time_range: normalizeTimeRangeInput(normalizedObj.time_range),
|
|
1364
|
+
stat_policy: normalizeStatPolicyInput(normalizedObj.stat_policy)
|
|
1309
1365
|
};
|
|
1310
1366
|
}
|
|
1311
1367
|
function coerceNumberLike(value) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1368
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1369
|
+
if (typeof parsed === "number" && Number.isFinite(parsed)) {
|
|
1370
|
+
return parsed;
|
|
1371
|
+
}
|
|
1372
|
+
if (typeof parsed === "string") {
|
|
1373
|
+
const trimmed = parsed.trim();
|
|
1314
1374
|
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
1315
1375
|
const parsed = Number(trimmed);
|
|
1316
1376
|
if (Number.isFinite(parsed)) {
|
|
@@ -1318,11 +1378,15 @@ function coerceNumberLike(value) {
|
|
|
1318
1378
|
}
|
|
1319
1379
|
}
|
|
1320
1380
|
}
|
|
1321
|
-
return
|
|
1381
|
+
return parsed;
|
|
1322
1382
|
}
|
|
1323
1383
|
function coerceBooleanLike(value) {
|
|
1324
|
-
|
|
1325
|
-
|
|
1384
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1385
|
+
if (typeof parsed === "boolean") {
|
|
1386
|
+
return parsed;
|
|
1387
|
+
}
|
|
1388
|
+
if (typeof parsed === "string") {
|
|
1389
|
+
const trimmed = parsed.trim().toLowerCase();
|
|
1326
1390
|
if (trimmed === "true") {
|
|
1327
1391
|
return true;
|
|
1328
1392
|
}
|
|
@@ -1330,28 +1394,41 @@ function coerceBooleanLike(value) {
|
|
|
1330
1394
|
return false;
|
|
1331
1395
|
}
|
|
1332
1396
|
}
|
|
1333
|
-
return
|
|
1397
|
+
return parsed;
|
|
1334
1398
|
}
|
|
1335
|
-
function
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1399
|
+
function parseJsonLikeDeep(value, maxDepth = 4) {
|
|
1400
|
+
let current = value;
|
|
1401
|
+
for (let i = 0; i < maxDepth; i += 1) {
|
|
1402
|
+
if (typeof current !== "string") {
|
|
1403
|
+
return current;
|
|
1404
|
+
}
|
|
1405
|
+
const trimmed = current.trim();
|
|
1406
|
+
if (!trimmed) {
|
|
1407
|
+
return current;
|
|
1408
|
+
}
|
|
1409
|
+
const singleQuoted = trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2;
|
|
1410
|
+
const candidate = singleQuoted ? trimmed.slice(1, -1) : trimmed;
|
|
1411
|
+
const shouldTryJson = candidate.startsWith("{") ||
|
|
1412
|
+
candidate.startsWith("[") ||
|
|
1413
|
+
(candidate.startsWith('"') && candidate.endsWith('"'));
|
|
1414
|
+
if (!shouldTryJson) {
|
|
1415
|
+
return current;
|
|
1416
|
+
}
|
|
1417
|
+
try {
|
|
1418
|
+
const parsed = JSON.parse(candidate);
|
|
1419
|
+
if (Object.is(parsed, current)) {
|
|
1420
|
+
return current;
|
|
1421
|
+
}
|
|
1422
|
+
current = parsed;
|
|
1423
|
+
}
|
|
1424
|
+
catch {
|
|
1425
|
+
return current;
|
|
1426
|
+
}
|
|
1351
1427
|
}
|
|
1428
|
+
return current;
|
|
1352
1429
|
}
|
|
1353
1430
|
function normalizeSelectorListInput(value) {
|
|
1354
|
-
const parsed =
|
|
1431
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1355
1432
|
if (Array.isArray(parsed)) {
|
|
1356
1433
|
return parsed.map((item) => coerceNumberLike(item));
|
|
1357
1434
|
}
|
|
@@ -1375,7 +1452,7 @@ function normalizeSelectorListInput(value) {
|
|
|
1375
1452
|
return parsed;
|
|
1376
1453
|
}
|
|
1377
1454
|
function normalizeIdArrayInput(value) {
|
|
1378
|
-
const parsed =
|
|
1455
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1379
1456
|
if (Array.isArray(parsed)) {
|
|
1380
1457
|
return parsed.map((item) => coerceNumberLike(item));
|
|
1381
1458
|
}
|
|
@@ -1389,7 +1466,7 @@ function normalizeIdArrayInput(value) {
|
|
|
1389
1466
|
return parsed;
|
|
1390
1467
|
}
|
|
1391
1468
|
function normalizeSortInput(value) {
|
|
1392
|
-
const parsed =
|
|
1469
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1393
1470
|
if (!Array.isArray(parsed)) {
|
|
1394
1471
|
return parsed;
|
|
1395
1472
|
}
|
|
@@ -1398,15 +1475,16 @@ function normalizeSortInput(value) {
|
|
|
1398
1475
|
if (!obj) {
|
|
1399
1476
|
return item;
|
|
1400
1477
|
}
|
|
1478
|
+
const normalizedObj = applyAliases(obj, { queId: "que_id", isAscend: "ascend" });
|
|
1401
1479
|
return {
|
|
1402
|
-
...
|
|
1403
|
-
que_id: coerceNumberLike(
|
|
1404
|
-
ascend: coerceBooleanLike(
|
|
1480
|
+
...normalizedObj,
|
|
1481
|
+
que_id: coerceNumberLike(normalizedObj.que_id),
|
|
1482
|
+
ascend: coerceBooleanLike(normalizedObj.ascend)
|
|
1405
1483
|
};
|
|
1406
1484
|
});
|
|
1407
1485
|
}
|
|
1408
1486
|
function normalizeFiltersInput(value) {
|
|
1409
|
-
const parsed =
|
|
1487
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1410
1488
|
if (parsed === undefined || parsed === null) {
|
|
1411
1489
|
return parsed;
|
|
1412
1490
|
}
|
|
@@ -1416,23 +1494,53 @@ function normalizeFiltersInput(value) {
|
|
|
1416
1494
|
if (!obj) {
|
|
1417
1495
|
return item;
|
|
1418
1496
|
}
|
|
1497
|
+
const normalizedObj = applyAliases(obj, {
|
|
1498
|
+
queId: "que_id",
|
|
1499
|
+
searchKey: "search_key",
|
|
1500
|
+
searchKeys: "search_keys",
|
|
1501
|
+
minValue: "min_value",
|
|
1502
|
+
maxValue: "max_value",
|
|
1503
|
+
searchOptions: "search_options",
|
|
1504
|
+
searchUserIds: "search_user_ids"
|
|
1505
|
+
});
|
|
1419
1506
|
return {
|
|
1420
|
-
...
|
|
1421
|
-
que_id: coerceNumberLike(
|
|
1422
|
-
scope: coerceNumberLike(
|
|
1423
|
-
search_options: normalizeIdArrayInput(
|
|
1507
|
+
...normalizedObj,
|
|
1508
|
+
que_id: coerceNumberLike(normalizedObj.que_id),
|
|
1509
|
+
scope: coerceNumberLike(normalizedObj.scope),
|
|
1510
|
+
search_options: normalizeIdArrayInput(normalizedObj.search_options)
|
|
1424
1511
|
};
|
|
1425
1512
|
});
|
|
1426
1513
|
}
|
|
1427
1514
|
function normalizeTimeRangeInput(value) {
|
|
1428
|
-
const parsed =
|
|
1515
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1516
|
+
const obj = asObject(parsed);
|
|
1517
|
+
if (!obj) {
|
|
1518
|
+
return parsed;
|
|
1519
|
+
}
|
|
1520
|
+
const normalizedObj = applyAliases(obj, {
|
|
1521
|
+
queId: "column",
|
|
1522
|
+
que_id: "column",
|
|
1523
|
+
timeZone: "timezone"
|
|
1524
|
+
});
|
|
1525
|
+
return {
|
|
1526
|
+
...normalizedObj,
|
|
1527
|
+
column: coerceNumberLike(normalizedObj.column)
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function normalizeStatPolicyInput(value) {
|
|
1531
|
+
const parsed = parseJsonLikeDeep(value);
|
|
1429
1532
|
const obj = asObject(parsed);
|
|
1430
1533
|
if (!obj) {
|
|
1431
1534
|
return parsed;
|
|
1432
1535
|
}
|
|
1536
|
+
const normalizedObj = applyAliases(obj, {
|
|
1537
|
+
includeNegative: "include_negative",
|
|
1538
|
+
includeNull: "include_null"
|
|
1539
|
+
});
|
|
1433
1540
|
return {
|
|
1434
|
-
...
|
|
1435
|
-
|
|
1541
|
+
...normalizedObj,
|
|
1542
|
+
include_negative: coerceBooleanLike(normalizedObj.include_negative),
|
|
1543
|
+
include_null: coerceBooleanLike(normalizedObj.include_null)
|
|
1436
1544
|
};
|
|
1437
1545
|
}
|
|
1438
1546
|
function resolveStartPage(pageNum, pageToken, appKey) {
|
|
@@ -1470,6 +1578,9 @@ function decodeContinuationToken(token) {
|
|
|
1470
1578
|
page_size: pageSize
|
|
1471
1579
|
};
|
|
1472
1580
|
}
|
|
1581
|
+
function isExecutionBudgetExceeded(startedAt) {
|
|
1582
|
+
return Date.now() - startedAt >= EXECUTION_BUDGET_MS;
|
|
1583
|
+
}
|
|
1473
1584
|
function buildEvidencePayload(state, sourcePages) {
|
|
1474
1585
|
return {
|
|
1475
1586
|
query_id: state.query_id,
|
|
@@ -1575,6 +1686,98 @@ function appendTimeRangeFilter(inputFilters, timeRange) {
|
|
|
1575
1686
|
}
|
|
1576
1687
|
return filters.length > 0 ? filters : undefined;
|
|
1577
1688
|
}
|
|
1689
|
+
function isLikelyDateLiteral(value) {
|
|
1690
|
+
if (!value) {
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
const trimmed = value.trim();
|
|
1694
|
+
if (!trimmed) {
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
return /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2})?$/.test(trimmed);
|
|
1698
|
+
}
|
|
1699
|
+
function isDateLikeQueType(queType) {
|
|
1700
|
+
if (typeof queType === "number" && Number.isInteger(queType)) {
|
|
1701
|
+
return queType === 4;
|
|
1702
|
+
}
|
|
1703
|
+
if (typeof queType === "string") {
|
|
1704
|
+
const trimmed = queType.trim().toLowerCase();
|
|
1705
|
+
if (trimmed === "date" || trimmed === "datetime") {
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1709
|
+
return Number(trimmed) === 4;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
function pickDateFieldSuggestion(index) {
|
|
1715
|
+
for (const field of index.byId.values()) {
|
|
1716
|
+
if (!isDateLikeQueType(field.queType) || field.queId === undefined || field.queId === null) {
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
const normalizedQueId = normalizeQueId(field.queId);
|
|
1720
|
+
if (typeof normalizedQueId === "number" && normalizedQueId <= 0) {
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
return {
|
|
1724
|
+
que_id: normalizedQueId,
|
|
1725
|
+
que_title: asNullableString(field.queTitle)
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
function hasDateLikeRangeFilters(filters) {
|
|
1731
|
+
return (filters ?? []).some((item) => item.que_id !== undefined &&
|
|
1732
|
+
(isLikelyDateLiteral(item.min_value) || isLikelyDateLiteral(item.max_value)));
|
|
1733
|
+
}
|
|
1734
|
+
function validateDateRangeFilters(filters, index, tool) {
|
|
1735
|
+
for (const filter of filters ?? []) {
|
|
1736
|
+
if (filter.que_id === undefined ||
|
|
1737
|
+
(!isLikelyDateLiteral(filter.min_value) && !isLikelyDateLiteral(filter.max_value))) {
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
let resolved;
|
|
1741
|
+
try {
|
|
1742
|
+
resolved = resolveFieldByKey(String(filter.que_id), index);
|
|
1743
|
+
}
|
|
1744
|
+
catch (error) {
|
|
1745
|
+
throw new InputValidationError({
|
|
1746
|
+
message: `Cannot resolve filter field "${String(filter.que_id)}"`,
|
|
1747
|
+
errorCode: "INVALID_FILTER_FIELD",
|
|
1748
|
+
fixHint: "Use qf_form_get to confirm exact que_id/que_title before passing filters.",
|
|
1749
|
+
details: {
|
|
1750
|
+
tool,
|
|
1751
|
+
filter,
|
|
1752
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
if (!resolved || resolved.queType === undefined || resolved.queType === null) {
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (isDateLikeQueType(resolved.queType)) {
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
const suggestion = pickDateFieldSuggestion(index);
|
|
1763
|
+
throw new InputValidationError({
|
|
1764
|
+
message: `Date-like filter range targets non-date field "${String(filter.que_id)}"`,
|
|
1765
|
+
errorCode: "FILTER_FIELD_TYPE_MISMATCH",
|
|
1766
|
+
fixHint: suggestion
|
|
1767
|
+
? `Use a date field for date range filters, e.g. que_id=${String(suggestion.que_id)} (${suggestion.que_title ?? "date field"}).`
|
|
1768
|
+
: "Use a date field (queType=4) for date range filters.",
|
|
1769
|
+
details: {
|
|
1770
|
+
tool,
|
|
1771
|
+
filter,
|
|
1772
|
+
resolved_field: {
|
|
1773
|
+
que_id: resolved.queId ?? null,
|
|
1774
|
+
que_title: asNullableString(resolved.queTitle),
|
|
1775
|
+
que_type: resolved.queType
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1578
1781
|
function buildRecordGetArgsFromQuery(args) {
|
|
1579
1782
|
if (args.apply_id === undefined) {
|
|
1580
1783
|
throw missingRequiredFieldError({
|
|
@@ -1617,8 +1820,14 @@ async function executeRecordsList(args) {
|
|
|
1617
1820
|
const requestedPages = args.requested_pages ?? 1;
|
|
1618
1821
|
const scanMaxPages = args.scan_max_pages ?? requestedPages;
|
|
1619
1822
|
const effectiveFilters = appendTimeRangeFilter(args.filters, args.time_range);
|
|
1823
|
+
if (hasDateLikeRangeFilters(effectiveFilters)) {
|
|
1824
|
+
const form = await getFormCached(args.app_key, args.user_id, false);
|
|
1825
|
+
const index = buildFieldIndex(form.result);
|
|
1826
|
+
validateDateRangeFilters(effectiveFilters, index, "qf_records_list");
|
|
1827
|
+
}
|
|
1620
1828
|
const normalizedSort = await normalizeListSort(args.sort, args.app_key, args.user_id);
|
|
1621
1829
|
const includeAnswers = true;
|
|
1830
|
+
const startedAt = Date.now();
|
|
1622
1831
|
let currentPage = pageNum;
|
|
1623
1832
|
let fetchedPages = 0;
|
|
1624
1833
|
let hasMore = false;
|
|
@@ -1629,6 +1838,11 @@ async function executeRecordsList(args) {
|
|
|
1629
1838
|
const sourcePages = [];
|
|
1630
1839
|
const collectedRawItems = [];
|
|
1631
1840
|
while (fetchedPages < requestedPages && fetchedPages < scanMaxPages) {
|
|
1841
|
+
if (fetchedPages > 0 && isExecutionBudgetExceeded(startedAt)) {
|
|
1842
|
+
hasMore = true;
|
|
1843
|
+
nextPageNum = currentPage;
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1632
1846
|
const payload = buildListPayload({
|
|
1633
1847
|
pageNum: currentPage,
|
|
1634
1848
|
pageSize,
|
|
@@ -1675,9 +1889,16 @@ async function executeRecordsList(args) {
|
|
|
1675
1889
|
selectColumns: args.select_columns
|
|
1676
1890
|
});
|
|
1677
1891
|
if (items.length > 0 && columnProjection.matchedAnswersCount === 0) {
|
|
1678
|
-
throw new
|
|
1679
|
-
|
|
1680
|
-
|
|
1892
|
+
throw new InputValidationError({
|
|
1893
|
+
message: `No answers matched select_columns (${args.select_columns
|
|
1894
|
+
.map((item) => String(item))
|
|
1895
|
+
.join(", ")}).`,
|
|
1896
|
+
errorCode: "COLUMN_SELECTOR_NOT_FOUND",
|
|
1897
|
+
fixHint: "Use qf_form_get to confirm que_id/que_title. If parameters were stringified, pass native JSON arrays (or plain arrays) for select_columns.",
|
|
1898
|
+
details: {
|
|
1899
|
+
select_columns: args.select_columns
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1681
1902
|
}
|
|
1682
1903
|
const fitted = fitListItemsWithinSize({
|
|
1683
1904
|
items: columnProjection.items,
|
|
@@ -1889,6 +2110,7 @@ async function executeRecordsSummary(args) {
|
|
|
1889
2110
|
...(args.time_range.to ? { max_value: args.time_range.to } : {})
|
|
1890
2111
|
});
|
|
1891
2112
|
}
|
|
2113
|
+
validateDateRangeFilters(summaryFilters, index, "qf_query(summary)");
|
|
1892
2114
|
const listState = {
|
|
1893
2115
|
query_id: queryId,
|
|
1894
2116
|
app_key: args.app_key,
|
|
@@ -1904,6 +2126,7 @@ async function executeRecordsSummary(args) {
|
|
|
1904
2126
|
: null
|
|
1905
2127
|
};
|
|
1906
2128
|
let currentPage = startPage;
|
|
2129
|
+
const startedAt = Date.now();
|
|
1907
2130
|
let scannedPages = 0;
|
|
1908
2131
|
let scannedRecords = 0;
|
|
1909
2132
|
let hasMore = false;
|
|
@@ -1916,6 +2139,11 @@ async function executeRecordsSummary(args) {
|
|
|
1916
2139
|
const rows = [];
|
|
1917
2140
|
const byDay = new Map();
|
|
1918
2141
|
while (scannedPages < requestedPages && scannedPages < scanMaxPages) {
|
|
2142
|
+
if (scannedPages > 0 && isExecutionBudgetExceeded(startedAt)) {
|
|
2143
|
+
hasMore = true;
|
|
2144
|
+
nextPageNum = currentPage;
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
1919
2147
|
const payload = buildListPayload({
|
|
1920
2148
|
pageNum: currentPage,
|
|
1921
2149
|
pageSize,
|
|
@@ -2123,6 +2351,7 @@ async function executeRecordsAggregate(args) {
|
|
|
2123
2351
|
...(args.time_range.to ? { max_value: args.time_range.to } : {})
|
|
2124
2352
|
});
|
|
2125
2353
|
}
|
|
2354
|
+
validateDateRangeFilters(aggregateFilters, index, "qf_records_aggregate");
|
|
2126
2355
|
const listState = {
|
|
2127
2356
|
query_id: queryId,
|
|
2128
2357
|
app_key: args.app_key,
|
|
@@ -2138,6 +2367,7 @@ async function executeRecordsAggregate(args) {
|
|
|
2138
2367
|
: null
|
|
2139
2368
|
};
|
|
2140
2369
|
let currentPage = startPage;
|
|
2370
|
+
const startedAt = Date.now();
|
|
2141
2371
|
let scannedPages = 0;
|
|
2142
2372
|
let scannedRecords = 0;
|
|
2143
2373
|
let hasMore = false;
|
|
@@ -2148,6 +2378,11 @@ async function executeRecordsAggregate(args) {
|
|
|
2148
2378
|
const sourcePages = [];
|
|
2149
2379
|
const groupStats = new Map();
|
|
2150
2380
|
while (scannedPages < requestedPages && scannedPages < scanMaxPages) {
|
|
2381
|
+
if (scannedPages > 0 && isExecutionBudgetExceeded(startedAt)) {
|
|
2382
|
+
hasMore = true;
|
|
2383
|
+
nextPageNum = currentPage;
|
|
2384
|
+
break;
|
|
2385
|
+
}
|
|
2151
2386
|
const payload = buildListPayload({
|
|
2152
2387
|
pageNum: currentPage,
|
|
2153
2388
|
pageSize,
|
|
@@ -2981,14 +3216,17 @@ function toErrorPayload(error) {
|
|
|
2981
3216
|
};
|
|
2982
3217
|
}
|
|
2983
3218
|
if (error instanceof QingflowApiError) {
|
|
3219
|
+
const timeoutHint = /timeout/i.test(error.message) || /timeout/i.test(error.errMsg);
|
|
2984
3220
|
return {
|
|
2985
3221
|
ok: false,
|
|
2986
|
-
error_code: "QINGFLOW_API_ERROR",
|
|
3222
|
+
error_code: timeoutHint ? "UPSTREAM_TIMEOUT" : "QINGFLOW_API_ERROR",
|
|
2987
3223
|
message: error.message,
|
|
2988
3224
|
err_code: error.errCode,
|
|
2989
3225
|
err_msg: error.errMsg || null,
|
|
2990
3226
|
http_status: error.httpStatus,
|
|
2991
|
-
fix_hint:
|
|
3227
|
+
fix_hint: timeoutHint
|
|
3228
|
+
? "Upstream request timed out. Reduce page_size/requested_pages, narrow filters, or continue with next_page_token."
|
|
3229
|
+
: "Check app_key/accessToken and request body against qf_form_get field definitions.",
|
|
2992
3230
|
next_page_token: null,
|
|
2993
3231
|
details: error.details ?? null
|
|
2994
3232
|
};
|