@detiq/crawler 1.1.9 → 1.1.10
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/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
package/dist/redis-frontier.js
CHANGED
|
@@ -1,146 +1,126 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
const FRONTIER_TTL = 86400;
|
|
2
|
+
class RedisCrawlFrontier {
|
|
3
|
+
constructor(jobId) {
|
|
4
|
+
this.jobId = jobId;
|
|
5
|
+
this.queueKey = `zeta:frontier:${jobId}:queue`;
|
|
6
|
+
this.visitedKey = `zeta:frontier:${jobId}:visited`;
|
|
7
|
+
this.useRedis = !!process.env.REDIS_URL;
|
|
8
|
+
}
|
|
9
|
+
jobId;
|
|
10
|
+
redis;
|
|
11
|
+
queueKey;
|
|
12
|
+
visitedKey;
|
|
13
|
+
inMemoryQueue = [];
|
|
14
|
+
inMemoryVisited = /* @__PURE__ */ new Set();
|
|
15
|
+
useRedis;
|
|
16
|
+
async init() {
|
|
17
|
+
if (!this.useRedis) return;
|
|
18
|
+
try {
|
|
19
|
+
const Redis = await import("ioredis").then((m) => m.default ?? m);
|
|
20
|
+
this.redis = new Redis(process.env.REDIS_URL, {
|
|
21
|
+
maxRetriesPerRequest: 2,
|
|
22
|
+
enableReadyCheck: false,
|
|
23
|
+
lazyConnect: true
|
|
24
|
+
});
|
|
25
|
+
await this.redis.connect().catch(() => {
|
|
26
|
+
this.useRedis = false;
|
|
27
|
+
this.redis = null;
|
|
28
|
+
});
|
|
29
|
+
} catch {
|
|
30
|
+
this.useRedis = false;
|
|
31
|
+
this.redis = null;
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
await this.redis.connect().catch(() => {
|
|
42
|
-
this.useRedis = false;
|
|
43
|
-
this.redis = null;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
this.useRedis = false;
|
|
48
|
-
this.redis = null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/** Add a URL to the frontier. No-op if already visited. Returns true if added. */
|
|
52
|
-
async push(url) {
|
|
53
|
-
if (this.useRedis && this.redis) {
|
|
54
|
-
try {
|
|
55
|
-
const isNew = await this.redis.sadd(this.visitedKey, url);
|
|
56
|
-
if (!isNew)
|
|
57
|
-
return false;
|
|
58
|
-
await this.redis.lpush(this.queueKey, url);
|
|
59
|
-
await this.redis.expire(this.queueKey, FRONTIER_TTL);
|
|
60
|
-
await this.redis.expire(this.visitedKey, FRONTIER_TTL);
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Fall through to in-memory on Redis error
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (this.inMemoryVisited.has(url))
|
|
68
|
-
return false;
|
|
69
|
-
this.inMemoryVisited.add(url);
|
|
70
|
-
this.inMemoryQueue.push(url);
|
|
33
|
+
}
|
|
34
|
+
/** Add a URL to the frontier. No-op if already visited. Returns true if added. */
|
|
35
|
+
async push(url) {
|
|
36
|
+
if (this.useRedis && this.redis) {
|
|
37
|
+
try {
|
|
38
|
+
const isNew = await this.redis.sadd(this.visitedKey, url);
|
|
39
|
+
if (!isNew) return false;
|
|
40
|
+
await this.redis.lpush(this.queueKey, url);
|
|
41
|
+
await this.redis.expire(this.queueKey, FRONTIER_TTL);
|
|
42
|
+
await this.redis.expire(this.visitedKey, FRONTIER_TTL);
|
|
71
43
|
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
72
46
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/** Check if a URL has already been visited/queued. */
|
|
87
|
-
async hasVisited(url) {
|
|
88
|
-
if (this.useRedis && this.redis) {
|
|
89
|
-
try {
|
|
90
|
-
return (await this.redis.sismember(this.visitedKey, url)) === 1;
|
|
91
|
-
}
|
|
92
|
-
catch { /* fall through */ }
|
|
93
|
-
}
|
|
94
|
-
return this.inMemoryVisited.has(url);
|
|
95
|
-
}
|
|
96
|
-
/** Current queue length. */
|
|
97
|
-
async length() {
|
|
98
|
-
if (this.useRedis && this.redis) {
|
|
99
|
-
try {
|
|
100
|
-
return await this.redis.llen(this.queueKey);
|
|
101
|
-
}
|
|
102
|
-
catch { /* fall through */ }
|
|
103
|
-
}
|
|
104
|
-
return this.inMemoryQueue.length;
|
|
47
|
+
if (this.inMemoryVisited.has(url)) return false;
|
|
48
|
+
this.inMemoryVisited.add(url);
|
|
49
|
+
this.inMemoryQueue.push(url);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/** Pop the next URL (FIFO/BFS). Returns null if queue empty. */
|
|
53
|
+
async shift() {
|
|
54
|
+
if (this.useRedis && this.redis) {
|
|
55
|
+
try {
|
|
56
|
+
const url = await this.redis.rpop(this.queueKey);
|
|
57
|
+
return url ?? null;
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
105
60
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
61
|
+
return this.inMemoryQueue.shift() ?? null;
|
|
62
|
+
}
|
|
63
|
+
/** Check if a URL has already been visited/queued. */
|
|
64
|
+
async hasVisited(url) {
|
|
65
|
+
if (this.useRedis && this.redis) {
|
|
66
|
+
try {
|
|
67
|
+
return await this.redis.sismember(this.visitedKey, url) === 1;
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
115
70
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
71
|
+
return this.inMemoryVisited.has(url);
|
|
72
|
+
}
|
|
73
|
+
/** Current queue length. */
|
|
74
|
+
async length() {
|
|
75
|
+
if (this.useRedis && this.redis) {
|
|
76
|
+
try {
|
|
77
|
+
return await this.redis.llen(this.queueKey);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
120
80
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
catch { /* ignore */ }
|
|
132
|
-
}
|
|
81
|
+
return this.inMemoryQueue.length;
|
|
82
|
+
}
|
|
83
|
+
/** Total visited count. */
|
|
84
|
+
async visitedCount() {
|
|
85
|
+
if (this.useRedis && this.redis) {
|
|
86
|
+
try {
|
|
87
|
+
return await this.redis.scard(this.visitedKey);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
133
90
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
91
|
+
return this.inMemoryVisited.size;
|
|
92
|
+
}
|
|
93
|
+
/** Seed initial URLs (used at job start). */
|
|
94
|
+
async seed(urls) {
|
|
95
|
+
for (const url of urls) await this.push(url);
|
|
96
|
+
}
|
|
97
|
+
/** Clean up all Redis keys for this frontier. Call at job completion. */
|
|
98
|
+
async cleanup() {
|
|
99
|
+
if (this.useRedis && this.redis) {
|
|
100
|
+
try {
|
|
101
|
+
await this.redis.del(this.queueKey, this.visitedKey);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await this.redis.quit();
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
141
108
|
}
|
|
109
|
+
}
|
|
110
|
+
/** Stats for monitoring. */
|
|
111
|
+
async stats() {
|
|
112
|
+
return {
|
|
113
|
+
backend: this.useRedis && this.redis ? "redis" : "memory",
|
|
114
|
+
queued: await this.length(),
|
|
115
|
+
visited: await this.visitedCount()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
142
118
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return !!process.env.REDIS_URL;
|
|
119
|
+
function isRedisFrontierEnabled() {
|
|
120
|
+
return !!process.env.REDIS_URL;
|
|
146
121
|
}
|
|
122
|
+
export {
|
|
123
|
+
RedisCrawlFrontier,
|
|
124
|
+
isRedisFrontierEnabled
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=redis-frontier.js.map
|
package/dist/redis-queue.js
CHANGED
|
@@ -1,152 +1,165 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Multiple crawler nodes can pull from the same stream — each job is
|
|
6
|
-
* delivered to exactly one consumer (at-least-once via ACK/PENDING).
|
|
7
|
-
*
|
|
8
|
-
* Used when REDIS_URL is set AND JOB_QUEUE_BACKEND=redis.
|
|
9
|
-
* Falls back to the Postgres-backed queue when Redis is unavailable.
|
|
10
|
-
*
|
|
11
|
-
* Stream key: zeta:crawl:stream
|
|
12
|
-
* Group name: zeta:crawl:workers
|
|
13
|
-
* Consumer id: zeta:worker:<hostname>:<pid>
|
|
14
|
-
*/
|
|
15
|
-
import { hostname } from 'node:os';
|
|
16
|
-
const STREAM_KEY = 'zeta:crawl:stream';
|
|
17
|
-
const GROUP_NAME = 'zeta:crawl:workers';
|
|
18
|
-
const MAX_LEN = 10_000; // approx cap on stream length
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
const STREAM_KEY = "zeta:crawl:stream";
|
|
3
|
+
const GROUP_NAME = "zeta:crawl:workers";
|
|
4
|
+
const MAX_LEN = 1e4;
|
|
19
5
|
function consumerId() {
|
|
20
|
-
|
|
6
|
+
return `zeta:worker:${hostname()}:${process.pid}`;
|
|
21
7
|
}
|
|
22
8
|
let ioredis;
|
|
23
9
|
async function getIoredis() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
if (!ioredis) {
|
|
11
|
+
ioredis = await import("ioredis").then((m) => m.default ?? m);
|
|
12
|
+
}
|
|
13
|
+
return ioredis;
|
|
28
14
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
15
|
+
async function createRedisQueue() {
|
|
16
|
+
const redisUrl = process.env.REDIS_URL;
|
|
17
|
+
if (!redisUrl) throw new Error("REDIS_URL env var required for redis queue backend");
|
|
18
|
+
const Redis = await getIoredis();
|
|
19
|
+
const redis = new Redis(redisUrl, {
|
|
20
|
+
maxRetriesPerRequest: 3,
|
|
21
|
+
enableReadyCheck: true,
|
|
22
|
+
lazyConnect: false
|
|
23
|
+
});
|
|
24
|
+
await redis.xgroup("CREATE", STREAM_KEY, GROUP_NAME, "$", "MKSTREAM").catch((e) => {
|
|
25
|
+
if (!e.message?.includes("BUSYGROUP")) throw e;
|
|
26
|
+
});
|
|
27
|
+
const consumer = consumerId();
|
|
28
|
+
return {
|
|
29
|
+
async enqueue(jobId, tenantId, projectId, payload = {}) {
|
|
30
|
+
const entryId = await redis.xadd(
|
|
31
|
+
STREAM_KEY,
|
|
32
|
+
"MAXLEN",
|
|
33
|
+
"~",
|
|
34
|
+
MAX_LEN,
|
|
35
|
+
"*",
|
|
36
|
+
"jobId",
|
|
37
|
+
jobId,
|
|
38
|
+
"tenantId",
|
|
39
|
+
tenantId,
|
|
40
|
+
"projectId",
|
|
41
|
+
projectId,
|
|
42
|
+
"payload",
|
|
43
|
+
JSON.stringify(payload),
|
|
44
|
+
"enqueuedAt",
|
|
45
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
46
|
+
);
|
|
47
|
+
return entryId;
|
|
48
|
+
},
|
|
49
|
+
async dequeue(timeoutMs = 5e3) {
|
|
50
|
+
const pending = await redis.xreadgroup(
|
|
51
|
+
"GROUP",
|
|
52
|
+
GROUP_NAME,
|
|
53
|
+
consumer,
|
|
54
|
+
"COUNT",
|
|
55
|
+
"1",
|
|
56
|
+
"STREAMS",
|
|
57
|
+
STREAM_KEY,
|
|
58
|
+
"0"
|
|
59
|
+
) ?? [];
|
|
60
|
+
let entries = pending?.[0]?.[1] ?? [];
|
|
61
|
+
if (!entries.length) {
|
|
62
|
+
const fresh = await redis.xreadgroup(
|
|
63
|
+
"GROUP",
|
|
64
|
+
GROUP_NAME,
|
|
65
|
+
consumer,
|
|
66
|
+
"COUNT",
|
|
67
|
+
"1",
|
|
68
|
+
"BLOCK",
|
|
69
|
+
timeoutMs,
|
|
70
|
+
"STREAMS",
|
|
71
|
+
STREAM_KEY,
|
|
72
|
+
">"
|
|
73
|
+
) ?? [];
|
|
74
|
+
entries = fresh?.[0]?.[1] ?? [];
|
|
75
|
+
}
|
|
76
|
+
if (!entries.length) return null;
|
|
77
|
+
const [entryId, fields] = entries[0];
|
|
78
|
+
const obj = {};
|
|
79
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
80
|
+
obj[fields[i]] = fields[i + 1];
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
id: entryId,
|
|
84
|
+
jobId: obj.jobId,
|
|
85
|
+
tenantId: obj.tenantId,
|
|
86
|
+
projectId: obj.projectId,
|
|
87
|
+
payload: JSON.parse(obj.payload ?? "{}"),
|
|
88
|
+
enqueuedAt: obj.enqueuedAt
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
async ack(streamEntryId) {
|
|
92
|
+
await redis.xack(STREAM_KEY, GROUP_NAME, streamEntryId);
|
|
93
|
+
},
|
|
94
|
+
async nack(streamEntryId) {
|
|
95
|
+
await redis.xack(STREAM_KEY, GROUP_NAME, streamEntryId);
|
|
96
|
+
},
|
|
97
|
+
async reclaimStale(maxIdleMs = 6e4) {
|
|
98
|
+
const result = await redis.xautoclaim(
|
|
99
|
+
STREAM_KEY,
|
|
100
|
+
GROUP_NAME,
|
|
101
|
+
consumer,
|
|
102
|
+
maxIdleMs,
|
|
103
|
+
"0-0",
|
|
104
|
+
"COUNT",
|
|
105
|
+
"10"
|
|
106
|
+
);
|
|
107
|
+
const entries = Array.isArray(result) ? result[1] ?? [] : [];
|
|
108
|
+
return entries.map(([entryId, fields]) => {
|
|
109
|
+
const obj = {};
|
|
110
|
+
for (let i = 0; i < fields.length; i += 2) obj[fields[i]] = fields[i + 1];
|
|
111
|
+
return {
|
|
112
|
+
id: entryId,
|
|
113
|
+
jobId: obj.jobId,
|
|
114
|
+
tenantId: obj.tenantId,
|
|
115
|
+
projectId: obj.projectId,
|
|
116
|
+
payload: JSON.parse(obj.payload ?? "{}"),
|
|
117
|
+
enqueuedAt: obj.enqueuedAt
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
async stats() {
|
|
122
|
+
const info = await redis.xinfo("GROUPS", STREAM_KEY);
|
|
123
|
+
const group = info.find((g) => {
|
|
124
|
+
const arr = Array.isArray(g) ? g : [];
|
|
125
|
+
for (let i = 0; i < arr.length; i += 2) {
|
|
126
|
+
if (arr[i] === "name" && arr[i + 1] === GROUP_NAME) return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
});
|
|
130
|
+
if (!group) return { pending: 0, delivered: 0, groupLag: 0 };
|
|
131
|
+
const flat = Array.isArray(group) ? group : [];
|
|
132
|
+
const get = (key) => {
|
|
133
|
+
for (let i = 0; i < flat.length - 1; i += 2) {
|
|
134
|
+
if (flat[i] === key) return Number(flat[i + 1]) || 0;
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
pending: get("pel-count"),
|
|
140
|
+
delivered: get("delivered-count"),
|
|
141
|
+
groupLag: get("lag")
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
async close() {
|
|
145
|
+
await redis.quit().catch(() => {
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
135
149
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
*/
|
|
139
|
-
export function isRedisQueueEnabled() {
|
|
140
|
-
return !!(process.env.REDIS_URL && process.env.JOB_QUEUE_BACKEND === 'redis');
|
|
150
|
+
function isRedisQueueEnabled() {
|
|
151
|
+
return !!(process.env.REDIS_URL && process.env.JOB_QUEUE_BACKEND === "redis");
|
|
141
152
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return createRedisQueue().catch((e) => {
|
|
149
|
-
console.error('[redis-queue] Failed to connect:', e.message);
|
|
150
|
-
return null;
|
|
151
|
-
});
|
|
153
|
+
async function maybeCreateRedisQueue() {
|
|
154
|
+
if (!isRedisQueueEnabled()) return null;
|
|
155
|
+
return createRedisQueue().catch((e) => {
|
|
156
|
+
console.error("[redis-queue] Failed to connect:", e.message);
|
|
157
|
+
return null;
|
|
158
|
+
});
|
|
152
159
|
}
|
|
160
|
+
export {
|
|
161
|
+
createRedisQueue,
|
|
162
|
+
isRedisQueueEnabled,
|
|
163
|
+
maybeCreateRedisQueue
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=redis-queue.js.map
|