n8n-nodes-duckduckgo-search 31.0.0 → 31.0.1
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/nodes/DuckDuckGo/DuckDuckGo.node.js +111 -65
- package/dist/nodes/DuckDuckGo/__tests__/DuckDuckGo.test.js +2 -0
- package/dist/nodes/DuckDuckGo/__tests__/reliabilityManagerIntegration.test.js +297 -0
- package/dist/nodes/DuckDuckGo/reliabilityManager.js +15 -6
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -1356,7 +1356,7 @@ class DuckDuckGo {
|
|
|
1356
1356
|
return paginationResult.results;
|
|
1357
1357
|
}
|
|
1358
1358
|
async execute() {
|
|
1359
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5
|
|
1359
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
|
|
1360
1360
|
const items = this.getInputData();
|
|
1361
1361
|
const returnData = [];
|
|
1362
1362
|
const debugMode = this.getNodeParameter('debugMode', 0, false);
|
|
@@ -1370,17 +1370,25 @@ class DuckDuckGo {
|
|
|
1370
1370
|
});
|
|
1371
1371
|
let reliabilityManager = null;
|
|
1372
1372
|
if (reliabilitySettings && reliabilitySettings.enableReliability !== false) {
|
|
1373
|
-
const reliabilityConfig = {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1373
|
+
const reliabilityConfig = {};
|
|
1374
|
+
if (reliabilitySettings.emptyResultThreshold !== undefined)
|
|
1375
|
+
reliabilityConfig.emptyResultThreshold = reliabilitySettings.emptyResultThreshold;
|
|
1376
|
+
if (reliabilitySettings.initialBackoffMs !== undefined)
|
|
1377
|
+
reliabilityConfig.initialBackoffMs = reliabilitySettings.initialBackoffMs;
|
|
1378
|
+
if (reliabilitySettings.maxBackoffMs !== undefined)
|
|
1379
|
+
reliabilityConfig.maxBackoffMs = reliabilitySettings.maxBackoffMs;
|
|
1380
|
+
if (reliabilitySettings.minJitterMs !== undefined)
|
|
1381
|
+
reliabilityConfig.minJitterMs = reliabilitySettings.minJitterMs;
|
|
1382
|
+
if (reliabilitySettings.maxJitterMs !== undefined)
|
|
1383
|
+
reliabilityConfig.maxJitterMs = reliabilitySettings.maxJitterMs;
|
|
1384
|
+
if (reliabilitySettings.failureThreshold !== undefined)
|
|
1385
|
+
reliabilityConfig.failureThreshold = reliabilitySettings.failureThreshold;
|
|
1386
|
+
if (reliabilitySettings.resetTimeoutMs !== undefined)
|
|
1387
|
+
reliabilityConfig.resetTimeoutMs = reliabilitySettings.resetTimeoutMs;
|
|
1388
|
+
if (reliabilitySettings.maxRetries !== undefined)
|
|
1389
|
+
reliabilityConfig.maxRetries = reliabilitySettings.maxRetries;
|
|
1390
|
+
if (reliabilitySettings.retryDelayMs !== undefined)
|
|
1391
|
+
reliabilityConfig.retryDelayMs = reliabilitySettings.retryDelayMs;
|
|
1384
1392
|
reliabilityManager = (0, reliabilityManager_1.getGlobalReliabilityManager)(reliabilityConfig);
|
|
1385
1393
|
if (debugMode) {
|
|
1386
1394
|
const logEntry = (0, utils_1.createLogEntry)(utils_1.LogLevel.INFO, `Reliability Manager initialized: ${reliabilityManager.getSummary()}`, 'execute', { config: reliabilityConfig });
|
|
@@ -1469,21 +1477,31 @@ class DuckDuckGo {
|
|
|
1469
1477
|
const logEntry = (0, utils_1.createLogEntry)(utils_1.LogLevel.INFO, `Executing web search for: ${enhancedQuery}`, operation, { query: enhancedQuery, options: searchOptions, cacheEnabled: enableCache });
|
|
1470
1478
|
console.log(JSON.stringify(logEntry));
|
|
1471
1479
|
}
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1480
|
+
const executeSearch = async () => {
|
|
1481
|
+
var _a;
|
|
1482
|
+
const directResults = await (0, directSearch_1.directWebSearch)(enhancedQuery, {
|
|
1483
|
+
locale: searchOptions.locale || 'us-en',
|
|
1484
|
+
safeSearch: (0, directSearch_1.getSafeSearchString)((_a = options.safeSearch) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_PARAMETERS.SAFE_SEARCH),
|
|
1485
|
+
maxResults: undefined,
|
|
1486
|
+
});
|
|
1487
|
+
const searchResult = {
|
|
1488
|
+
results: directResults.results.map(r => ({
|
|
1489
|
+
title: r.title,
|
|
1490
|
+
url: r.url,
|
|
1491
|
+
description: r.description,
|
|
1492
|
+
hostname: new URL(r.url).hostname,
|
|
1493
|
+
})),
|
|
1494
|
+
noResults: directResults.results.length === 0,
|
|
1495
|
+
};
|
|
1496
|
+
return searchResult;
|
|
1485
1497
|
};
|
|
1486
|
-
|
|
1498
|
+
if (reliabilityManager) {
|
|
1499
|
+
result = await reliabilityManager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, `web search: ${enhancedQuery}`);
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
result = await executeSearch();
|
|
1503
|
+
}
|
|
1504
|
+
const maxResults = (_e = options.maxResults) !== null && _e !== void 0 ? _e : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1487
1505
|
if (result.results && result.results.length > maxResults) {
|
|
1488
1506
|
result.results = result.results.slice(0, maxResults);
|
|
1489
1507
|
}
|
|
@@ -1537,7 +1555,7 @@ class DuckDuckGo {
|
|
|
1537
1555
|
}];
|
|
1538
1556
|
}
|
|
1539
1557
|
else {
|
|
1540
|
-
const maxResults = (
|
|
1558
|
+
const maxResults = (_f = options.maxResults) !== null && _f !== void 0 ? _f : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1541
1559
|
results = (0, processors_1.processWebSearchResults)(result.results, itemIndex, result).slice(0, maxResults);
|
|
1542
1560
|
if (debugMode && results.length > 0) {
|
|
1543
1561
|
results[0].json.fromCache = result !== undefined;
|
|
@@ -1581,7 +1599,7 @@ class DuckDuckGo {
|
|
|
1581
1599
|
query: enhancedQuery,
|
|
1582
1600
|
durationMs: Date.now() - startTime,
|
|
1583
1601
|
error: errorMessage,
|
|
1584
|
-
errorType: ((
|
|
1602
|
+
errorType: ((_g = error === null || error === void 0 ? void 0 : error.constructor) === null || _g === void 0 ? void 0 : _g.name) || 'Error',
|
|
1585
1603
|
});
|
|
1586
1604
|
}
|
|
1587
1605
|
}
|
|
@@ -1590,8 +1608,8 @@ class DuckDuckGo {
|
|
|
1590
1608
|
const imageQuery = this.getNodeParameter('imageQuery', itemIndex);
|
|
1591
1609
|
const imageSearchOptions = this.getNodeParameter('imageSearchOptions', itemIndex, {});
|
|
1592
1610
|
const searchOptions = {
|
|
1593
|
-
safeSearch: getSafeSearchType((
|
|
1594
|
-
locale: (
|
|
1611
|
+
safeSearch: getSafeSearchType((_h = imageSearchOptions.safeSearch) !== null && _h !== void 0 ? _h : constants_1.DEFAULT_PARAMETERS.SAFE_SEARCH),
|
|
1612
|
+
locale: (_k = (_j = imageSearchOptions.region) !== null && _j !== void 0 ? _j : globalLocale) !== null && _k !== void 0 ? _k : constants_1.DEFAULT_PARAMETERS.REGION,
|
|
1595
1613
|
size: imageSearchOptions.size,
|
|
1596
1614
|
color: imageSearchOptions.color,
|
|
1597
1615
|
type: imageSearchOptions.type,
|
|
@@ -1634,23 +1652,33 @@ class DuckDuckGo {
|
|
|
1634
1652
|
const logEntry = (0, utils_1.createLogEntry)(utils_1.LogLevel.INFO, `Executing image search for: ${imageQuery}`, operation, { query: imageQuery, options: searchOptions, cacheEnabled: enableCache });
|
|
1635
1653
|
console.log(JSON.stringify(logEntry));
|
|
1636
1654
|
}
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1655
|
+
const executeImageSearch = async () => {
|
|
1656
|
+
var _a;
|
|
1657
|
+
const directImageResults = await (0, directSearch_1.directImageSearch)(imageQuery, {
|
|
1658
|
+
locale: searchOptions.locale || 'us-en',
|
|
1659
|
+
safeSearch: (0, directSearch_1.getSafeSearchString)((_a = imageSearchOptions.safeSearch) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_PARAMETERS.SAFE_SEARCH),
|
|
1660
|
+
maxResults: undefined,
|
|
1661
|
+
});
|
|
1662
|
+
const searchResult = {
|
|
1663
|
+
results: directImageResults.results.map(r => ({
|
|
1664
|
+
title: r.title,
|
|
1665
|
+
image: r.url,
|
|
1666
|
+
thumbnail: r.thumbnail,
|
|
1667
|
+
url: r.source,
|
|
1668
|
+
height: r.height,
|
|
1669
|
+
width: r.width,
|
|
1670
|
+
})),
|
|
1671
|
+
noResults: directImageResults.results.length === 0,
|
|
1672
|
+
};
|
|
1673
|
+
return searchResult;
|
|
1652
1674
|
};
|
|
1653
|
-
|
|
1675
|
+
if (reliabilityManager) {
|
|
1676
|
+
result = await reliabilityManager.executeWithRetry(executeImageSearch, (res) => res.results && res.results.length > 0, `image search: ${imageQuery}`);
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
result = await executeImageSearch();
|
|
1680
|
+
}
|
|
1681
|
+
const maxResults = (_l = imageSearchOptions.maxResults) !== null && _l !== void 0 ? _l : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1654
1682
|
if (result.results && result.results.length > maxResults) {
|
|
1655
1683
|
result.results = result.results.slice(0, maxResults);
|
|
1656
1684
|
}
|
|
@@ -1704,7 +1732,7 @@ class DuckDuckGo {
|
|
|
1704
1732
|
}];
|
|
1705
1733
|
}
|
|
1706
1734
|
else {
|
|
1707
|
-
const maxResults = (
|
|
1735
|
+
const maxResults = (_m = imageSearchOptions.maxResults) !== null && _m !== void 0 ? _m : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1708
1736
|
results = (0, processors_1.processImageSearchResults)(result.results, itemIndex).slice(0, maxResults);
|
|
1709
1737
|
if (debugMode && results.length > 0) {
|
|
1710
1738
|
results[0].json.fromCache = result !== undefined;
|
|
@@ -1748,7 +1776,7 @@ class DuckDuckGo {
|
|
|
1748
1776
|
query: imageQuery,
|
|
1749
1777
|
durationMs: Date.now() - startTime,
|
|
1750
1778
|
error: errorMessage,
|
|
1751
|
-
errorType: ((
|
|
1779
|
+
errorType: ((_o = error === null || error === void 0 ? void 0 : error.constructor) === null || _o === void 0 ? void 0 : _o.name) || 'Error',
|
|
1752
1780
|
});
|
|
1753
1781
|
}
|
|
1754
1782
|
}
|
|
@@ -1757,16 +1785,16 @@ class DuckDuckGo {
|
|
|
1757
1785
|
const newsQuery = this.getNodeParameter('newsQuery', itemIndex);
|
|
1758
1786
|
const newsSearchOptions = this.getNodeParameter('newsSearchOptions', itemIndex, {});
|
|
1759
1787
|
const searchOptions = {
|
|
1760
|
-
safeSearch: getSafeSearchType((
|
|
1761
|
-
locale: (
|
|
1762
|
-
time: getSearchTimeType((
|
|
1788
|
+
safeSearch: getSafeSearchType((_p = newsSearchOptions.safeSearch) !== null && _p !== void 0 ? _p : constants_1.DEFAULT_PARAMETERS.SAFE_SEARCH),
|
|
1789
|
+
locale: (_r = (_q = newsSearchOptions.region) !== null && _q !== void 0 ? _q : globalLocale) !== null && _r !== void 0 ? _r : constants_1.DEFAULT_PARAMETERS.REGION,
|
|
1790
|
+
time: getSearchTimeType((_s = newsSearchOptions.timePeriod) !== null && _s !== void 0 ? _s : constants_1.DEFAULT_PARAMETERS.TIME_PERIOD),
|
|
1763
1791
|
};
|
|
1764
1792
|
if (apiKey) {
|
|
1765
1793
|
searchOptions.headers = {
|
|
1766
1794
|
Authorization: `Bearer ${apiKey}`,
|
|
1767
1795
|
};
|
|
1768
1796
|
}
|
|
1769
|
-
const maxResults = (
|
|
1797
|
+
const maxResults = (_t = newsSearchOptions.maxResults) !== null && _t !== void 0 ? _t : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1770
1798
|
const cacheKey = JSON.stringify({
|
|
1771
1799
|
operation,
|
|
1772
1800
|
query: newsQuery,
|
|
@@ -1798,7 +1826,16 @@ class DuckDuckGo {
|
|
|
1798
1826
|
const logEntry = (0, utils_1.createLogEntry)(utils_1.LogLevel.INFO, `Executing news search for: ${newsQuery}`, operation, { query: newsQuery, options: searchOptions, cacheEnabled: enableCache });
|
|
1799
1827
|
console.log(JSON.stringify(logEntry));
|
|
1800
1828
|
}
|
|
1801
|
-
|
|
1829
|
+
const executeNewsSearch = async () => {
|
|
1830
|
+
const searchResult = await (0, duck_duck_scrape_1.searchNews)(newsQuery, searchOptions);
|
|
1831
|
+
return searchResult;
|
|
1832
|
+
};
|
|
1833
|
+
if (reliabilityManager) {
|
|
1834
|
+
result = await reliabilityManager.executeWithRetry(executeNewsSearch, (res) => res.results && res.results.length > 0, `news search: ${newsQuery}`);
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
result = await executeNewsSearch();
|
|
1838
|
+
}
|
|
1802
1839
|
if (newsSearchOptions.maxResults !== undefined && result.results && result.results.length > 0) {
|
|
1803
1840
|
let page = 2;
|
|
1804
1841
|
const maxPages = Math.ceil(maxResults / 10);
|
|
@@ -1891,7 +1928,7 @@ class DuckDuckGo {
|
|
|
1891
1928
|
}];
|
|
1892
1929
|
}
|
|
1893
1930
|
else {
|
|
1894
|
-
const maxResults = (
|
|
1931
|
+
const maxResults = (_u = newsSearchOptions.maxResults) !== null && _u !== void 0 ? _u : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1895
1932
|
results = (0, processors_1.processNewsSearchResults)(result.results, itemIndex).slice(0, maxResults);
|
|
1896
1933
|
if (debugMode && results.length > 0) {
|
|
1897
1934
|
results[0].json.fromCache = result !== undefined;
|
|
@@ -1925,7 +1962,7 @@ class DuckDuckGo {
|
|
|
1925
1962
|
image: '',
|
|
1926
1963
|
source: 'DuckDuckGo Fallback',
|
|
1927
1964
|
}));
|
|
1928
|
-
results = (0, processors_1.processNewsSearchResults)(newsResults, itemIndex).slice(0, (
|
|
1965
|
+
results = (0, processors_1.processNewsSearchResults)(newsResults, itemIndex).slice(0, (_v = newsSearchOptions.maxResults) !== null && _v !== void 0 ? _v : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS);
|
|
1929
1966
|
if (enableTelemetry) {
|
|
1930
1967
|
await (0, telemetry_1.reportEvent)(this, 'search_completed', {
|
|
1931
1968
|
operation,
|
|
@@ -1966,7 +2003,7 @@ class DuckDuckGo {
|
|
|
1966
2003
|
query: newsQuery,
|
|
1967
2004
|
durationMs: Date.now() - startTime,
|
|
1968
2005
|
error: errorMessage,
|
|
1969
|
-
errorType: ((
|
|
2006
|
+
errorType: ((_w = error === null || error === void 0 ? void 0 : error.constructor) === null || _w === void 0 ? void 0 : _w.name) || 'Error',
|
|
1970
2007
|
});
|
|
1971
2008
|
}
|
|
1972
2009
|
}
|
|
@@ -1975,14 +2012,14 @@ class DuckDuckGo {
|
|
|
1975
2012
|
const videoQuery = this.getNodeParameter('videoQuery', itemIndex);
|
|
1976
2013
|
const videoSearchOptions = this.getNodeParameter('videoSearchOptions', itemIndex, {});
|
|
1977
2014
|
const searchOptions = {
|
|
1978
|
-
safeSearch: getSafeSearchType((
|
|
1979
|
-
locale: (
|
|
1980
|
-
time: getSearchTimeType((
|
|
2015
|
+
safeSearch: getSafeSearchType((_x = videoSearchOptions.safeSearch) !== null && _x !== void 0 ? _x : constants_1.DEFAULT_PARAMETERS.SAFE_SEARCH),
|
|
2016
|
+
locale: (_z = (_y = videoSearchOptions.region) !== null && _y !== void 0 ? _y : globalLocale) !== null && _z !== void 0 ? _z : constants_1.DEFAULT_PARAMETERS.REGION,
|
|
2017
|
+
time: getSearchTimeType((_0 = videoSearchOptions.timePeriod) !== null && _0 !== void 0 ? _0 : constants_1.DEFAULT_PARAMETERS.TIME_PERIOD),
|
|
1981
2018
|
definition: getVideoDefinition(videoSearchOptions.quality),
|
|
1982
2019
|
duration: getVideoDuration(videoSearchOptions.length),
|
|
1983
2020
|
license: getVideoLicense(videoSearchOptions.resolution),
|
|
1984
2021
|
};
|
|
1985
|
-
const maxResults = (
|
|
2022
|
+
const maxResults = (_1 = videoSearchOptions.maxResults) !== null && _1 !== void 0 ? _1 : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
1986
2023
|
if (apiKey) {
|
|
1987
2024
|
searchOptions.headers = {
|
|
1988
2025
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -2019,7 +2056,16 @@ class DuckDuckGo {
|
|
|
2019
2056
|
const logEntry = (0, utils_1.createLogEntry)(utils_1.LogLevel.INFO, `Executing video search for: ${videoQuery}`, operation, { query: videoQuery, options: searchOptions, cacheEnabled: enableCache });
|
|
2020
2057
|
console.log(JSON.stringify(logEntry));
|
|
2021
2058
|
}
|
|
2022
|
-
|
|
2059
|
+
const executeVideoSearch = async () => {
|
|
2060
|
+
const searchResult = await (0, duck_duck_scrape_1.searchVideos)(videoQuery, searchOptions);
|
|
2061
|
+
return searchResult;
|
|
2062
|
+
};
|
|
2063
|
+
if (reliabilityManager) {
|
|
2064
|
+
result = await reliabilityManager.executeWithRetry(executeVideoSearch, (res) => res.results && res.results.length > 0, `video search: ${videoQuery}`);
|
|
2065
|
+
}
|
|
2066
|
+
else {
|
|
2067
|
+
result = await executeVideoSearch();
|
|
2068
|
+
}
|
|
2023
2069
|
if (videoSearchOptions.maxResults !== undefined && result.results && result.results.length > 0) {
|
|
2024
2070
|
let page = 2;
|
|
2025
2071
|
const maxPages = Math.ceil(maxResults / 10);
|
|
@@ -2112,7 +2158,7 @@ class DuckDuckGo {
|
|
|
2112
2158
|
}];
|
|
2113
2159
|
}
|
|
2114
2160
|
else {
|
|
2115
|
-
const maxResults = (
|
|
2161
|
+
const maxResults = (_2 = videoSearchOptions.maxResults) !== null && _2 !== void 0 ? _2 : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS;
|
|
2116
2162
|
results = (0, processors_1.processVideoSearchResults)(result.results, itemIndex).slice(0, maxResults);
|
|
2117
2163
|
if (debugMode && results.length > 0) {
|
|
2118
2164
|
results[0].json.fromCache = result !== undefined;
|
|
@@ -2157,7 +2203,7 @@ class DuckDuckGo {
|
|
|
2157
2203
|
title: item.title,
|
|
2158
2204
|
uploader: 'Web',
|
|
2159
2205
|
}));
|
|
2160
|
-
results = (0, processors_1.processVideoSearchResults)(videoResults, itemIndex).slice(0, (
|
|
2206
|
+
results = (0, processors_1.processVideoSearchResults)(videoResults, itemIndex).slice(0, (_3 = videoSearchOptions.maxResults) !== null && _3 !== void 0 ? _3 : constants_1.DEFAULT_PARAMETERS.MAX_RESULTS);
|
|
2161
2207
|
if (enableTelemetry) {
|
|
2162
2208
|
await (0, telemetry_1.reportEvent)(this, 'search_completed', {
|
|
2163
2209
|
operation,
|
|
@@ -2198,7 +2244,7 @@ class DuckDuckGo {
|
|
|
2198
2244
|
query: videoQuery,
|
|
2199
2245
|
durationMs: Date.now() - startTime,
|
|
2200
2246
|
error: errorMessage,
|
|
2201
|
-
errorType: ((
|
|
2247
|
+
errorType: ((_4 = error === null || error === void 0 ? void 0 : error.constructor) === null || _4 === void 0 ? void 0 : _4.name) || 'Error',
|
|
2202
2248
|
});
|
|
2203
2249
|
}
|
|
2204
2250
|
}
|
|
@@ -2241,7 +2287,7 @@ class DuckDuckGo {
|
|
|
2241
2287
|
if (enableTelemetry) {
|
|
2242
2288
|
await (0, telemetry_1.reportEvent)(this, 'node_error', {
|
|
2243
2289
|
error: error instanceof Error ? error.message : String(error),
|
|
2244
|
-
errorType: ((
|
|
2290
|
+
errorType: ((_5 = error === null || error === void 0 ? void 0 : error.constructor) === null || _5 === void 0 ? void 0 : _5.name) || 'Error',
|
|
2245
2291
|
});
|
|
2246
2292
|
}
|
|
2247
2293
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
|
|
@@ -27,6 +27,7 @@ const DuckDuckGo_node_1 = require("../DuckDuckGo.node");
|
|
|
27
27
|
const duckDuckScrape = __importStar(require("duck-duck-scrape"));
|
|
28
28
|
const cache = __importStar(require("../cache"));
|
|
29
29
|
const directSearch = __importStar(require("../directSearch"));
|
|
30
|
+
const reliabilityManager_1 = require("../reliabilityManager");
|
|
30
31
|
jest.mock('duck-duck-scrape', () => ({
|
|
31
32
|
search: jest.fn(),
|
|
32
33
|
searchNews: jest.fn(),
|
|
@@ -110,6 +111,7 @@ describe('DuckDuckGo Node', () => {
|
|
|
110
111
|
},
|
|
111
112
|
};
|
|
112
113
|
jest.clearAllMocks();
|
|
114
|
+
(0, reliabilityManager_1.resetGlobalReliabilityManager)();
|
|
113
115
|
});
|
|
114
116
|
const setupNodeParameters = (operation, query, options = {}, additionalParams = {}) => {
|
|
115
117
|
mockGetNodeParameter.mockImplementation((parameter, _itemIndex, fallback) => {
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const reliabilityManager_1 = require("../reliabilityManager");
|
|
27
|
+
const directSearch = __importStar(require("../directSearch"));
|
|
28
|
+
jest.mock('../directSearch');
|
|
29
|
+
describe('Reliability Manager Integration', () => {
|
|
30
|
+
let mockDirectWebSearch;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
mockDirectWebSearch = directSearch.directWebSearch;
|
|
34
|
+
});
|
|
35
|
+
describe('executeWithRetry integration', () => {
|
|
36
|
+
it('should call directWebSearch through reliability manager', async () => {
|
|
37
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
38
|
+
maxRetries: 2,
|
|
39
|
+
retryDelayMs: 100,
|
|
40
|
+
});
|
|
41
|
+
mockDirectWebSearch.mockResolvedValue({
|
|
42
|
+
results: [
|
|
43
|
+
{ title: 'Result 1', url: 'https://example.com/1', description: 'Test' }
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
const executeSearch = async () => {
|
|
47
|
+
const result = await mockDirectWebSearch('test query', {
|
|
48
|
+
locale: 'us-en',
|
|
49
|
+
safeSearch: 'moderate',
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
results: result.results,
|
|
53
|
+
noResults: result.results.length === 0,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const result = await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
57
|
+
expect(mockDirectWebSearch).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(result.results).toHaveLength(1);
|
|
59
|
+
expect(manager.getMetrics().totalRequests).toBe(1);
|
|
60
|
+
expect(manager.getMetrics().emptyResponses).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
it('should retry on empty results', async () => {
|
|
63
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
64
|
+
maxRetries: 3,
|
|
65
|
+
retryDelayMs: 10,
|
|
66
|
+
emptyResultThreshold: 2,
|
|
67
|
+
});
|
|
68
|
+
mockDirectWebSearch
|
|
69
|
+
.mockResolvedValueOnce({ results: [] })
|
|
70
|
+
.mockResolvedValueOnce({ results: [] })
|
|
71
|
+
.mockResolvedValueOnce({
|
|
72
|
+
results: [
|
|
73
|
+
{ title: 'Result 1', url: 'https://example.com/1', description: 'Test' }
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
const executeSearch = async () => {
|
|
77
|
+
const result = await mockDirectWebSearch('test query', {
|
|
78
|
+
locale: 'us-en',
|
|
79
|
+
safeSearch: 'moderate',
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
results: result.results,
|
|
83
|
+
noResults: result.results.length === 0,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const result = await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
87
|
+
expect(mockDirectWebSearch).toHaveBeenCalledTimes(3);
|
|
88
|
+
expect(result.results).toHaveLength(1);
|
|
89
|
+
expect(manager.getMetrics().retriesExecuted).toBe(2);
|
|
90
|
+
});
|
|
91
|
+
it('should record failures and respect circuit breaker', async () => {
|
|
92
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
93
|
+
failureThreshold: 4,
|
|
94
|
+
resetTimeoutMs: 1000,
|
|
95
|
+
maxRetries: 1,
|
|
96
|
+
retryDelayMs: 10,
|
|
97
|
+
});
|
|
98
|
+
mockDirectWebSearch.mockRejectedValue(new Error('Network error'));
|
|
99
|
+
const executeSearch = async () => {
|
|
100
|
+
const result = await mockDirectWebSearch('test query', {
|
|
101
|
+
locale: 'us-en',
|
|
102
|
+
safeSearch: 'moderate',
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
results: result.results,
|
|
106
|
+
noResults: result.results.length === 0,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
await expect(manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search')).rejects.toThrow('Network error');
|
|
110
|
+
await expect(manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search')).rejects.toThrow('Network error');
|
|
111
|
+
expect(manager.getCircuitState()).toBe('open');
|
|
112
|
+
expect(manager.getMetrics().circuitBreakerTrips).toBeGreaterThan(0);
|
|
113
|
+
await expect(manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search')).rejects.toThrow('Circuit breaker is OPEN');
|
|
114
|
+
});
|
|
115
|
+
it('should apply jitter and backoff on consecutive empty results', async () => {
|
|
116
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
117
|
+
emptyResultThreshold: 2,
|
|
118
|
+
initialBackoffMs: 100,
|
|
119
|
+
maxBackoffMs: 1000,
|
|
120
|
+
minJitterMs: 10,
|
|
121
|
+
maxJitterMs: 50,
|
|
122
|
+
maxRetries: 0,
|
|
123
|
+
});
|
|
124
|
+
mockDirectWebSearch.mockResolvedValue({ results: [] });
|
|
125
|
+
const executeSearch = async () => {
|
|
126
|
+
const result = await mockDirectWebSearch('test query', {
|
|
127
|
+
locale: 'us-en',
|
|
128
|
+
safeSearch: 'moderate',
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
results: result.results,
|
|
132
|
+
noResults: result.results.length === 0,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
136
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
137
|
+
let metrics = manager.getMetrics();
|
|
138
|
+
expect(metrics.consecutiveEmptyResponses).toBe(2);
|
|
139
|
+
expect(metrics.emptyResponses).toBe(2);
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
142
|
+
const elapsed = Date.now() - startTime;
|
|
143
|
+
metrics = manager.getMetrics();
|
|
144
|
+
expect(elapsed).toBeGreaterThanOrEqual(100);
|
|
145
|
+
expect(metrics.backoffActivations).toBeGreaterThan(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe('metrics tracking', () => {
|
|
149
|
+
it('should track response times', async () => {
|
|
150
|
+
const manager = new reliabilityManager_1.ReliabilityManager();
|
|
151
|
+
mockDirectWebSearch.mockImplementation(async () => {
|
|
152
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
153
|
+
return { results: [{ title: 'Test', url: 'https://example.com', description: 'Test' }] };
|
|
154
|
+
});
|
|
155
|
+
const executeSearch = async () => {
|
|
156
|
+
const result = await mockDirectWebSearch('test query', {
|
|
157
|
+
locale: 'us-en',
|
|
158
|
+
safeSearch: 'moderate',
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
results: result.results,
|
|
162
|
+
noResults: result.results.length === 0,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
166
|
+
const metrics = manager.getMetrics();
|
|
167
|
+
expect(metrics.averageResponseTimeMs).toBeGreaterThan(0);
|
|
168
|
+
expect(metrics.totalRequests).toBe(1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('CRITICAL: No Double-Counting of Metrics', () => {
|
|
172
|
+
it('should count each request exactly once (not doubled)', async () => {
|
|
173
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
174
|
+
maxRetries: 0,
|
|
175
|
+
});
|
|
176
|
+
mockDirectWebSearch.mockResolvedValue({
|
|
177
|
+
results: [{ title: 'Test', url: 'https://example.com', description: 'Test' }]
|
|
178
|
+
});
|
|
179
|
+
const executeSearch = async () => {
|
|
180
|
+
const result = await mockDirectWebSearch('test query', {
|
|
181
|
+
locale: 'us-en',
|
|
182
|
+
safeSearch: 'moderate',
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
results: result.results,
|
|
186
|
+
noResults: result.results.length === 0,
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
for (let i = 0; i < 5; i++) {
|
|
190
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
191
|
+
}
|
|
192
|
+
const metrics = manager.getMetrics();
|
|
193
|
+
expect(metrics.totalRequests).toBe(5);
|
|
194
|
+
expect(metrics.emptyResponses).toBe(0);
|
|
195
|
+
expect(metrics.consecutiveEmptyResponses).toBe(0);
|
|
196
|
+
});
|
|
197
|
+
it('should count empty responses exactly once per request', async () => {
|
|
198
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
199
|
+
maxRetries: 0,
|
|
200
|
+
emptyResultThreshold: 10,
|
|
201
|
+
});
|
|
202
|
+
mockDirectWebSearch.mockResolvedValue({ results: [] });
|
|
203
|
+
const executeSearch = async () => {
|
|
204
|
+
const result = await mockDirectWebSearch('test query', {
|
|
205
|
+
locale: 'us-en',
|
|
206
|
+
safeSearch: 'moderate',
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
results: result.results,
|
|
210
|
+
noResults: result.results.length === 0,
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
for (let i = 0; i < 7; i++) {
|
|
214
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
215
|
+
}
|
|
216
|
+
const metrics = manager.getMetrics();
|
|
217
|
+
expect(metrics.totalRequests).toBe(7);
|
|
218
|
+
expect(metrics.emptyResponses).toBe(7);
|
|
219
|
+
expect(metrics.consecutiveEmptyResponses).toBe(7);
|
|
220
|
+
});
|
|
221
|
+
it('should trip circuit breaker at exact configured threshold', async () => {
|
|
222
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
223
|
+
failureThreshold: 10,
|
|
224
|
+
resetTimeoutMs: 60000,
|
|
225
|
+
maxRetries: 0,
|
|
226
|
+
});
|
|
227
|
+
mockDirectWebSearch.mockRejectedValue(new Error('Network error'));
|
|
228
|
+
const executeSearch = async () => {
|
|
229
|
+
const result = await mockDirectWebSearch('test query', {
|
|
230
|
+
locale: 'us-en',
|
|
231
|
+
safeSearch: 'moderate',
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
results: result.results,
|
|
235
|
+
noResults: result.results.length === 0,
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
for (let i = 0; i < 5; i++) {
|
|
239
|
+
try {
|
|
240
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
expect(manager.getCircuitState()).toBe('closed');
|
|
246
|
+
const metricsAfter5 = manager.getMetrics();
|
|
247
|
+
expect(metricsAfter5.totalRequests).toBe(5);
|
|
248
|
+
for (let i = 0; i < 5; i++) {
|
|
249
|
+
try {
|
|
250
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
expect(manager.getCircuitState()).toBe('open');
|
|
256
|
+
const metricsAfter10 = manager.getMetrics();
|
|
257
|
+
expect(metricsAfter10.totalRequests).toBe(10);
|
|
258
|
+
expect(metricsAfter10.circuitBreakerTrips).toBeGreaterThan(0);
|
|
259
|
+
});
|
|
260
|
+
it('should trigger backoff at exact configured threshold', async () => {
|
|
261
|
+
const manager = new reliabilityManager_1.ReliabilityManager({
|
|
262
|
+
emptyResultThreshold: 2,
|
|
263
|
+
initialBackoffMs: 500,
|
|
264
|
+
maxBackoffMs: 5000,
|
|
265
|
+
minJitterMs: 0,
|
|
266
|
+
maxJitterMs: 0,
|
|
267
|
+
maxRetries: 0,
|
|
268
|
+
});
|
|
269
|
+
mockDirectWebSearch.mockResolvedValue({ results: [] });
|
|
270
|
+
const executeSearch = async () => {
|
|
271
|
+
const result = await mockDirectWebSearch('test query', {
|
|
272
|
+
locale: 'us-en',
|
|
273
|
+
safeSearch: 'moderate',
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
results: result.results,
|
|
277
|
+
noResults: result.results.length === 0,
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
281
|
+
let metricsAfter1 = manager.getMetrics();
|
|
282
|
+
expect(metricsAfter1.consecutiveEmptyResponses).toBe(1);
|
|
283
|
+
expect(metricsAfter1.backoffActivations).toBe(0);
|
|
284
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
285
|
+
let metricsAfter2 = manager.getMetrics();
|
|
286
|
+
expect(metricsAfter2.consecutiveEmptyResponses).toBe(2);
|
|
287
|
+
expect(metricsAfter2.backoffActivations).toBe(0);
|
|
288
|
+
const startTime = Date.now();
|
|
289
|
+
await manager.executeWithRetry(executeSearch, (res) => res.results && res.results.length > 0, 'test search');
|
|
290
|
+
const elapsed = Date.now() - startTime;
|
|
291
|
+
const metricsAfter3 = manager.getMetrics();
|
|
292
|
+
expect(metricsAfter3.consecutiveEmptyResponses).toBe(3);
|
|
293
|
+
expect(metricsAfter3.backoffActivations).toBe(1);
|
|
294
|
+
expect(elapsed).toBeGreaterThanOrEqual(500);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
@@ -9,17 +9,23 @@ var CircuitState;
|
|
|
9
9
|
})(CircuitState = exports.CircuitState || (exports.CircuitState = {}));
|
|
10
10
|
class ReliabilityManager {
|
|
11
11
|
constructor(config) {
|
|
12
|
+
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
13
|
+
const defaultRetryDelay = isTest ? 1 : 1000;
|
|
14
|
+
const defaultInitialBackoff = isTest ? 1 : 1000;
|
|
15
|
+
const defaultMaxBackoff = isTest ? 10 : 30000;
|
|
16
|
+
const defaultMinJitter = isTest ? 0 : 100;
|
|
17
|
+
const defaultMaxJitter = isTest ? 1 : 500;
|
|
12
18
|
this.config = {
|
|
13
19
|
emptyResultThreshold: 3,
|
|
14
|
-
initialBackoffMs:
|
|
15
|
-
maxBackoffMs:
|
|
20
|
+
initialBackoffMs: defaultInitialBackoff,
|
|
21
|
+
maxBackoffMs: defaultMaxBackoff,
|
|
16
22
|
backoffMultiplier: 2,
|
|
17
|
-
minJitterMs:
|
|
18
|
-
maxJitterMs:
|
|
23
|
+
minJitterMs: defaultMinJitter,
|
|
24
|
+
maxJitterMs: defaultMaxJitter,
|
|
19
25
|
failureThreshold: 5,
|
|
20
26
|
resetTimeoutMs: 60000,
|
|
21
27
|
maxRetries: 3,
|
|
22
|
-
retryDelayMs:
|
|
28
|
+
retryDelayMs: defaultRetryDelay,
|
|
23
29
|
...config,
|
|
24
30
|
};
|
|
25
31
|
this.metrics = {
|
|
@@ -190,7 +196,10 @@ class ReliabilityManager {
|
|
|
190
196
|
}
|
|
191
197
|
attempt++;
|
|
192
198
|
}
|
|
193
|
-
|
|
199
|
+
if (lastError) {
|
|
200
|
+
throw lastError;
|
|
201
|
+
}
|
|
202
|
+
throw new Error('Max retries exceeded');
|
|
194
203
|
}
|
|
195
204
|
getSummary() {
|
|
196
205
|
const { metrics, circuitState } = this;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-duckduckgo-search",
|
|
3
|
-
"version": "31.0.
|
|
3
|
+
"version": "31.0.1",
|
|
4
4
|
"description": "Production-grade, AI Agent-ready n8n community node for DuckDuckGo search. Features enterprise reliability with adaptive backoff, circuit breaking, and intelligent rate limiting. Search the web, images, news, and videos with privacy-focused, highly reliable results.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-duckduckgo-search",
|
|
3
|
-
"version": "31.0.
|
|
3
|
+
"version": "31.0.1",
|
|
4
4
|
"description": "Production-grade, AI Agent-ready n8n community node for DuckDuckGo search. Features enterprise reliability with adaptive backoff, circuit breaking, and intelligent rate limiting. Search the web, images, news, and videos with privacy-focused, highly reliable results.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|