@detiq/crawler 1.1.23 → 1.1.25
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/crawler.js +173 -28
- package/dist/crawler.js.map +2 -2
- package/dist/extractors/branding-extractor.js +104 -0
- package/dist/extractors/branding-extractor.js.map +7 -0
- package/dist/extractors/summary-extractor.js +36 -0
- package/dist/extractors/summary-extractor.js.map +7 -0
- package/dist/frontier.js +20 -2
- package/dist/frontier.js.map +2 -2
- package/dist/incremental-diff.js +76 -0
- package/dist/incremental-diff.js.map +2 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +2 -2
- package/dist/interactive-probe.js +108 -17
- package/dist/interactive-probe.js.map +2 -2
- package/dist/job-queue.js +408 -61
- package/dist/job-queue.js.map +2 -2
- package/dist/sqs-queue.js +78 -10
- package/dist/sqs-queue.js.map +2 -2
- package/dist/worker.js +7 -7
- package/dist/worker.js.map +2 -2
- package/package.json +1 -1
- package/src/crawler.ts +208 -30
- package/src/extractors/branding-extractor.ts +159 -0
- package/src/extractors/summary-extractor.ts +49 -0
- package/src/frontier.ts +31 -1
- package/src/incremental-diff.ts +138 -0
- package/src/index.ts +1 -1
- package/src/interactive-probe.ts +129 -20
- package/src/job-queue.ts +489 -63
- package/src/sqs-queue.ts +92 -12
- package/src/worker.ts +13 -11
- package/types/crawler.d.ts +20 -0
- package/types/extractors/branding-extractor.d.ts +46 -0
- package/types/extractors/summary-extractor.d.ts +10 -0
- package/types/frontier.d.ts +9 -1
- package/types/incremental-diff.d.ts +57 -0
- package/types/index.d.ts +1 -1
- package/types/interactive-probe.d.ts +5 -1
- package/types/job-queue.d.ts +53 -0
- package/types/sqs-queue.d.ts +12 -1
package/src/sqs-queue.ts
CHANGED
|
@@ -1,21 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
SQSClient,
|
|
3
|
+
SendMessageCommand,
|
|
4
|
+
ReceiveMessageCommand,
|
|
5
|
+
DeleteMessageCommand,
|
|
6
|
+
GetQueueAttributesCommand,
|
|
7
|
+
} from '@aws-sdk/client-sqs';
|
|
2
8
|
|
|
3
9
|
const sqs = new SQSClient({ region: process.env.AWS_REGION ?? 'us-east-1' });
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
11
|
+
function getQueueUrl(): string {
|
|
12
|
+
const url = process.env.SQS_QUEUE_URL ?? process.env.SQS_CRAWL_QUEUE_URL;
|
|
13
|
+
if (!url) throw new Error('SQS_QUEUE_URL not configured');
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getDlqUrl(): string | null {
|
|
18
|
+
return process.env.SQS_DLQ_URL ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function sendJobToSqs(jobId: string): Promise<void> {
|
|
8
22
|
await sqs.send(new SendMessageCommand({
|
|
9
|
-
QueueUrl:
|
|
10
|
-
MessageBody: JSON.stringify({ jobId
|
|
23
|
+
QueueUrl: getQueueUrl(),
|
|
24
|
+
MessageBody: JSON.stringify({ jobId }),
|
|
11
25
|
}));
|
|
12
26
|
}
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
/** Returns the approximate number of messages in the SQS DLQ. */
|
|
29
|
+
export async function getDlqStats(): Promise<{ approximateMessageCount: number; dlqUrl: string | null }> {
|
|
30
|
+
const dlqUrl = getDlqUrl();
|
|
31
|
+
if (!dlqUrl) return { approximateMessageCount: 0, dlqUrl: null };
|
|
32
|
+
try {
|
|
33
|
+
const { Attributes = {} } = await sqs.send(new GetQueueAttributesCommand({
|
|
34
|
+
QueueUrl: dlqUrl,
|
|
35
|
+
AttributeNames: ['ApproximateNumberOfMessages'],
|
|
36
|
+
}));
|
|
37
|
+
return {
|
|
38
|
+
approximateMessageCount: parseInt(Attributes.ApproximateNumberOfMessages ?? '0', 10),
|
|
39
|
+
dlqUrl,
|
|
40
|
+
};
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
console.error('[sqs-dlq] getDlqStats error:', err.message);
|
|
43
|
+
return { approximateMessageCount: 0, dlqUrl };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Redrive up to `maxMessages` messages from the SQS DLQ back to the main queue.
|
|
49
|
+
* Each message is received from the DLQ, re-sent to the main queue, then deleted
|
|
50
|
+
* from the DLQ. Returns the number of messages successfully redriven.
|
|
51
|
+
*/
|
|
52
|
+
export async function redriveFromDlq(maxMessages = 10): Promise<number> {
|
|
53
|
+
const dlqUrl = getDlqUrl();
|
|
54
|
+
if (!dlqUrl) {
|
|
55
|
+
console.warn('[sqs-dlq] SQS_DLQ_URL not configured — cannot redrive');
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
const mainUrl = getQueueUrl();
|
|
59
|
+
let totalRedriven = 0;
|
|
60
|
+
|
|
61
|
+
while (totalRedriven < maxMessages) {
|
|
62
|
+
const batch = Math.min(10, maxMessages - totalRedriven);
|
|
63
|
+
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({
|
|
64
|
+
QueueUrl: dlqUrl,
|
|
65
|
+
MaxNumberOfMessages: batch,
|
|
66
|
+
WaitTimeSeconds: 1,
|
|
67
|
+
}));
|
|
68
|
+
if (Messages.length === 0) break;
|
|
69
|
+
|
|
70
|
+
for (const msg of Messages) {
|
|
71
|
+
try {
|
|
72
|
+
await sqs.send(new SendMessageCommand({
|
|
73
|
+
QueueUrl: mainUrl,
|
|
74
|
+
MessageBody: msg.Body!,
|
|
75
|
+
}));
|
|
76
|
+
await sqs.send(new DeleteMessageCommand({
|
|
77
|
+
QueueUrl: dlqUrl,
|
|
78
|
+
ReceiptHandle: msg.ReceiptHandle!,
|
|
79
|
+
}));
|
|
80
|
+
totalRedriven++;
|
|
81
|
+
console.log('[sqs-dlq] redriven message:', msg.MessageId);
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
console.error('[sqs-dlq] redrive error for message', msg.MessageId, ':', err.message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return totalRedriven;
|
|
89
|
+
}
|
|
17
90
|
|
|
18
|
-
|
|
91
|
+
export async function startSqsWorker(): Promise<void> {
|
|
92
|
+
const queueUrl = getQueueUrl();
|
|
93
|
+
const { claimSpecificJob, dispatchClaimedJob } = await import('./job-queue.js');
|
|
19
94
|
console.log('[sqs-worker] starting, queue:', queueUrl);
|
|
20
95
|
|
|
21
96
|
while (true) {
|
|
@@ -31,14 +106,19 @@ export async function startSqsWorker(): Promise<void> {
|
|
|
31
106
|
try {
|
|
32
107
|
const data = JSON.parse(msg.Body!);
|
|
33
108
|
jobId = data.jobId;
|
|
34
|
-
|
|
109
|
+
// Atomically move QUEUED → RUNNING; returns null if another worker beat us.
|
|
110
|
+
const claimed = await claimSpecificJob(jobId);
|
|
111
|
+
if (claimed) {
|
|
112
|
+
await dispatchClaimedJob(claimed);
|
|
113
|
+
}
|
|
114
|
+
// Delete whether we ran it or another worker did — message is handled either way.
|
|
35
115
|
await sqs.send(new DeleteMessageCommand({
|
|
36
116
|
QueueUrl: queueUrl,
|
|
37
117
|
ReceiptHandle: msg.ReceiptHandle!,
|
|
38
118
|
}));
|
|
39
119
|
} catch (err: any) {
|
|
40
120
|
console.error(`[sqs-worker] job ${jobId} failed — leaving for SQS redrive:`, err.message);
|
|
41
|
-
// Do NOT delete — SQS
|
|
121
|
+
// Do NOT delete — SQS visibility timeout expires and DLQ handles retry.
|
|
42
122
|
}
|
|
43
123
|
}));
|
|
44
124
|
} catch (err: any) {
|
package/src/worker.ts
CHANGED
|
@@ -14,8 +14,19 @@ import { logger } from './logger.js';
|
|
|
14
14
|
async function main() {
|
|
15
15
|
logger.info('[worker] starting standalone crawler worker process');
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (process.env.JOB_QUEUE_BACKEND === 'sqs') {
|
|
18
|
+
// SQS mode: messages arrive via long-poll; claimSpecificJob atomically moves
|
|
19
|
+
// QUEUED→RUNNING before dispatch, so multiple worker replicas are safe.
|
|
20
|
+
// DB poll loop also runs as a safety net for jobs whose SQS message was lost.
|
|
21
|
+
const { startSqsWorker } = await import('./sqs-queue.js');
|
|
22
|
+
startSqsWorker().catch((err) => {
|
|
23
|
+
logger.error({ err: String(err) }, '[worker] SQS worker crashed');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// DB-backed poll loop — primary in non-SQS mode, safety net in SQS mode.
|
|
29
|
+
// Claims all job types. Safe to run in many worker replicas (FOR UPDATE SKIP LOCKED).
|
|
19
30
|
startJobWorkerLoop();
|
|
20
31
|
|
|
21
32
|
// Daily data-retention purge: runs immediately on startup then every 24 h.
|
|
@@ -28,15 +39,6 @@ async function main() {
|
|
|
28
39
|
};
|
|
29
40
|
runPurge();
|
|
30
41
|
setInterval(runPurge, 24 * 60 * 60 * 1000);
|
|
31
|
-
|
|
32
|
-
// Optional: also consume the SQS transport for horizontally-scaled CRAWL fleets.
|
|
33
|
-
if (process.env.JOB_QUEUE_BACKEND === 'sqs') {
|
|
34
|
-
const { startSqsWorker } = await import('./sqs-queue.js');
|
|
35
|
-
startSqsWorker().catch((err) => {
|
|
36
|
-
logger.error({ err: String(err) }, '[worker] SQS worker crashed');
|
|
37
|
-
process.exit(1);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
process.on('SIGTERM', () => { logger.info('[worker] SIGTERM received, exiting'); process.exit(0); });
|
package/types/crawler.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ interface PerformanceMetric {
|
|
|
21
21
|
ttfb?: number;
|
|
22
22
|
cls?: number;
|
|
23
23
|
}
|
|
24
|
+
export interface DiscoveredLinkMeta {
|
|
25
|
+
url: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
landmark?: string;
|
|
28
|
+
source: 'DOM' | 'AI' | 'INTERACTIVE';
|
|
29
|
+
}
|
|
24
30
|
/**
|
|
25
31
|
* Lightweight pre-crawl link discovery: loads the app URL in a headless (proxy-aware) browser
|
|
26
32
|
* and returns same-site `<a href>` links. Complements sitemap discovery for SPA/app sites that
|
|
@@ -60,6 +66,13 @@ export declare function crawlProject(opts: {
|
|
|
60
66
|
startUrls?: string[];
|
|
61
67
|
/** When provided, crawl ONLY these exact URLs (no link-following, no full screen clear). Used for selected-screen recrawl. */
|
|
62
68
|
selectiveUrls?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* URL path patterns that bypass the CUID/UUID generated-ID filter during BFS link discovery.
|
|
71
|
+
* Use {id} as a placeholder for any generated path segment (CUID, UUID, numeric ID, etc.).
|
|
72
|
+
* Example: ['/projects/{id}/tests', '/workspaces/{id}/settings']
|
|
73
|
+
* Without this, all URLs containing generated IDs are silently skipped by the BFS.
|
|
74
|
+
*/
|
|
75
|
+
seedPathPatterns?: string[];
|
|
63
76
|
/** When true, run the interactive probe after static crawl to capture real validation errors, toasts, and modals. */
|
|
64
77
|
interactive?: boolean;
|
|
65
78
|
/** Optional abort signal — abort() cancels the crawl between page navigations. */
|
|
@@ -143,10 +156,17 @@ export declare function crawlProject(opts: {
|
|
|
143
156
|
autoThrottle?: boolean;
|
|
144
157
|
/** Regex pattern — only crawl URLs whose path+query match this pattern. */
|
|
145
158
|
urlFilter?: string;
|
|
159
|
+
/** Glob patterns — only enqueue URLs whose pathname matches at least one (supports * and **). */
|
|
160
|
+
includePatterns?: string[];
|
|
161
|
+
/** Glob patterns — skip URLs whose pathname matches any of these (supports * and **). */
|
|
162
|
+
excludePatterns?: string[];
|
|
146
163
|
/** Already-captured URLs from a previous failed job — pre-seeded into visited map so they're never re-crawled. */
|
|
147
164
|
skipUrls?: string[];
|
|
148
165
|
/** DOM hashes collected in a prior segment — prevents re-screenshotting structurally identical pages across segments. */
|
|
149
166
|
knownDomHashes?: string[];
|
|
167
|
+
/** When false, disables the puppeteer-extra stealth plugin (playwright-extra) for this crawl.
|
|
168
|
+
* Default true — stealth is applied unless explicitly set to false. */
|
|
169
|
+
stealth?: boolean;
|
|
150
170
|
/** Called every 10 successful screen captures with current visited URLs and remaining queue — used for checkpoint persistence. */
|
|
151
171
|
onCheckpoint?: (visitedUrls: string[], pendingQueue: string[]) => Promise<void>;
|
|
152
172
|
/** Discovery mode only: called after each page with the current set of discovered original URLs — enables streaming partial results to callers. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
export interface BrandingColors {
|
|
3
|
+
primary?: string;
|
|
4
|
+
accent?: string;
|
|
5
|
+
background?: string;
|
|
6
|
+
textPrimary?: string;
|
|
7
|
+
link?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BrandingFont {
|
|
10
|
+
family: string;
|
|
11
|
+
role: 'heading' | 'body';
|
|
12
|
+
}
|
|
13
|
+
export interface BrandingImages {
|
|
14
|
+
logo?: string;
|
|
15
|
+
favicon?: string;
|
|
16
|
+
ogImage?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface BrandingPersonality {
|
|
19
|
+
tone?: string;
|
|
20
|
+
energy?: 'high' | 'medium' | 'low';
|
|
21
|
+
targetAudience?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface BrandingResult {
|
|
24
|
+
colorScheme: 'light' | 'dark';
|
|
25
|
+
colors: BrandingColors;
|
|
26
|
+
fonts: BrandingFont[];
|
|
27
|
+
images: BrandingImages;
|
|
28
|
+
personality?: BrandingPersonality;
|
|
29
|
+
designSystem?: {
|
|
30
|
+
framework?: string;
|
|
31
|
+
componentLibrary?: string;
|
|
32
|
+
};
|
|
33
|
+
metadata?: {
|
|
34
|
+
title?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
themeColor?: string;
|
|
37
|
+
language?: string;
|
|
38
|
+
};
|
|
39
|
+
confidence: {
|
|
40
|
+
colors: number;
|
|
41
|
+
fonts: number;
|
|
42
|
+
images: number;
|
|
43
|
+
overall: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function extractBranding(page: Page): Promise<BrandingResult>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SummaryResult {
|
|
2
|
+
summary: string;
|
|
3
|
+
wordCount: number;
|
|
4
|
+
method: 'llm' | 'truncated';
|
|
5
|
+
model?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function extractSummary(markdown: string, options?: {
|
|
8
|
+
model?: string;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
}): Promise<SummaryResult>;
|
package/types/frontier.d.ts
CHANGED
|
@@ -9,12 +9,20 @@
|
|
|
9
9
|
* array it replaces, so BFS (the default) has zero behavior change.
|
|
10
10
|
*/
|
|
11
11
|
export type TraversalStrategy = 'BFS' | 'DFS' | 'BEST_FIRST';
|
|
12
|
+
/**
|
|
13
|
+
* Simple glob-like pattern matching against a URL's pathname.
|
|
14
|
+
* Supports `*` (matches any single path segment characters, not `/`)
|
|
15
|
+
* and `**` (matches anything including `/`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function matchesPattern(url: string, patterns: string[]): boolean;
|
|
12
18
|
export declare class CrawlFrontier {
|
|
13
19
|
private items;
|
|
14
20
|
private readonly strategy;
|
|
15
21
|
private readonly keywords;
|
|
16
22
|
private readonly urlFilter;
|
|
17
|
-
|
|
23
|
+
private readonly includePatterns;
|
|
24
|
+
private readonly excludePatterns;
|
|
25
|
+
constructor(strategy: TraversalStrategy, seedUrls: string[], keywords?: string[], urlFilter?: string, includePatterns?: string[], excludePatterns?: string[]);
|
|
18
26
|
get length(): number;
|
|
19
27
|
/**
|
|
20
28
|
* P3b: sparse-parent boost — if the parent page had fewer than 5 interactive
|
|
@@ -57,3 +57,60 @@ export declare function partitionByChange(urls: string[], priorHashes: Map<strin
|
|
|
57
57
|
toProcess: string[];
|
|
58
58
|
toSkip: string[];
|
|
59
59
|
};
|
|
60
|
+
/** A single line-level change in text content (markdown, html, summary). */
|
|
61
|
+
export interface TextChange {
|
|
62
|
+
type: 'added' | 'removed';
|
|
63
|
+
line: number;
|
|
64
|
+
content: string;
|
|
65
|
+
}
|
|
66
|
+
/** A change to a specific JSON path between two scraped JSON objects. */
|
|
67
|
+
export interface JsonChange {
|
|
68
|
+
path: string;
|
|
69
|
+
old: unknown;
|
|
70
|
+
new: unknown;
|
|
71
|
+
}
|
|
72
|
+
/** Set-based change for collections like links or images. */
|
|
73
|
+
export interface SetChange {
|
|
74
|
+
added: string[];
|
|
75
|
+
removed: string[];
|
|
76
|
+
}
|
|
77
|
+
/** Screenshot pixel-level change. changed is percentage (0-100). */
|
|
78
|
+
export interface ImageChange {
|
|
79
|
+
size: number;
|
|
80
|
+
changed: number;
|
|
81
|
+
mask?: string;
|
|
82
|
+
}
|
|
83
|
+
/** All possible diffs for a single monitor tick. */
|
|
84
|
+
export interface MonitorDiffs {
|
|
85
|
+
markdown?: TextChange[];
|
|
86
|
+
html?: TextChange[];
|
|
87
|
+
json?: JsonChange[];
|
|
88
|
+
summary?: TextChange[];
|
|
89
|
+
links?: SetChange;
|
|
90
|
+
images?: SetChange;
|
|
91
|
+
branding?: JsonChange[];
|
|
92
|
+
screenshot?: ImageChange;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Compute line-level text diffs between two strings.
|
|
96
|
+
* Returns TextChange[] showing exactly which lines were added or removed.
|
|
97
|
+
* No external dependencies — pure JS Myers-inspired algorithm.
|
|
98
|
+
*/
|
|
99
|
+
export declare function computeTextDiffs(previous: string, current: string): TextChange[];
|
|
100
|
+
/**
|
|
101
|
+
* Compute set-based diffs for URL collections (links, images).
|
|
102
|
+
*/
|
|
103
|
+
export declare function computeSetDiffs(previous: string[], current: string[]): SetChange;
|
|
104
|
+
/**
|
|
105
|
+
* Flatten a JSON object into dot-notation path/value pairs.
|
|
106
|
+
* Used to compute JsonChange[] between two JSON extraction results.
|
|
107
|
+
*/
|
|
108
|
+
export declare function flattenJson(obj: unknown, prefix?: string): Record<string, unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Compute JSON path-level diffs between two extraction results.
|
|
111
|
+
*/
|
|
112
|
+
export declare function computeJsonDiffs(previous: unknown, current: unknown): JsonChange[];
|
|
113
|
+
/**
|
|
114
|
+
* Count total number of changes across all diff types.
|
|
115
|
+
*/
|
|
116
|
+
export declare function countMonitorDiffs(diffs: MonitorDiffs): number;
|
package/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export * from './visual-diff-verdict.js';
|
|
|
3
3
|
export * from './auth.js';
|
|
4
4
|
export * from './screen-reader.js';
|
|
5
5
|
export * from './job-queue.js';
|
|
6
|
-
export { startSqsWorker } from './sqs-queue.js';
|
|
6
|
+
export { startSqsWorker, getDlqStats, redriveFromDlq } from './sqs-queue.js';
|
|
7
7
|
export * from './session-capture.js';
|
|
8
8
|
export * from './session-export.js';
|
|
9
9
|
export * from './test-executor.js';
|
|
@@ -37,11 +37,15 @@ export interface ModalObservation {
|
|
|
37
37
|
title: string;
|
|
38
38
|
content: string;
|
|
39
39
|
fields: string[];
|
|
40
|
+
stateId?: string;
|
|
41
|
+
domSnapshot?: string;
|
|
40
42
|
}
|
|
41
43
|
export interface ConditionalReveal {
|
|
42
44
|
triggerElement: string;
|
|
43
45
|
triggerValue: string;
|
|
44
46
|
revealedText: string;
|
|
47
|
+
stateId?: string;
|
|
48
|
+
domSnapshot?: string;
|
|
45
49
|
}
|
|
46
50
|
export interface TooltipCapture {
|
|
47
51
|
triggerLabel: string;
|
|
@@ -117,7 +121,7 @@ export declare function probeAllScreens(page: any, tenantId: string, projectId:
|
|
|
117
121
|
url: string;
|
|
118
122
|
screenId?: string;
|
|
119
123
|
elements?: any[];
|
|
120
|
-
}>, onProgress?: (pct: number, step: string) => Promise<void
|
|
124
|
+
}>, onProgress?: (pct: number, step: string) => Promise<void>, jobId?: string): Promise<void>;
|
|
121
125
|
export interface FocusableElement {
|
|
122
126
|
tag: string;
|
|
123
127
|
role: string | null;
|
package/types/job-queue.d.ts
CHANGED
|
@@ -67,6 +67,52 @@ export interface PlatformJobConfig {
|
|
|
67
67
|
ssoScope?: string;
|
|
68
68
|
/** HTTP/HTTPS/SOCKS5 proxy URL — routes browser through this proxy for bot-protected sites. */
|
|
69
69
|
proxyUrl?: string;
|
|
70
|
+
/** HTML extraction mode. 'normal'=raw, 'reader'=Readability article, 'prune'=strip nav/ads. Default: 'normal'. */
|
|
71
|
+
htmlMode?: 'normal' | 'reader' | 'prune';
|
|
72
|
+
/** Per-request screenshot dimensions and quality. Defaults: 1440×900, quality 80. */
|
|
73
|
+
screenshotConfig?: {
|
|
74
|
+
fullPage?: boolean;
|
|
75
|
+
width?: number;
|
|
76
|
+
height?: number;
|
|
77
|
+
quality?: number;
|
|
78
|
+
};
|
|
79
|
+
/** Enable/disable Playwright stealth plugin. Default: true (existing behaviour). */
|
|
80
|
+
stealth?: boolean;
|
|
81
|
+
/** Mock mode — intercept all navigation and return synthetic content. Good for CI. */
|
|
82
|
+
mock?: boolean | {
|
|
83
|
+
minKb?: number;
|
|
84
|
+
maxKb?: number;
|
|
85
|
+
minSleep?: number;
|
|
86
|
+
maxSleep?: number;
|
|
87
|
+
};
|
|
88
|
+
/** MIME type allowlist — crawler skips URLs returning disallowed content types. */
|
|
89
|
+
allowedTypes?: string[];
|
|
90
|
+
/** Glob-style include patterns for URL paths (e.g. '/blog/*'). Only matching paths crawled. */
|
|
91
|
+
includePatterns?: string[];
|
|
92
|
+
/** Glob-style exclude patterns for URL paths (e.g. '/admin/*'). Matching paths skipped. */
|
|
93
|
+
excludePatterns?: string[];
|
|
94
|
+
/** Allow crawler to follow links to external domains. Default: false. */
|
|
95
|
+
allowExternal?: boolean;
|
|
96
|
+
/** Requested output formats for a single-pass multi-format extraction. */
|
|
97
|
+
requestedFormats?: Array<'markdown' | 'html' | 'links' | 'images' | 'summary' | 'json' | 'branding' | 'screenshot'>;
|
|
98
|
+
/** LLM chunker configuration for AI extraction passes. */
|
|
99
|
+
chunkerConfig?: {
|
|
100
|
+
size?: number | 'dynamic';
|
|
101
|
+
overlap?: number;
|
|
102
|
+
};
|
|
103
|
+
/** ISO-3166-1 alpha-2 country code for geo-targeted proxy routing (e.g. 'us', 'gb'). */
|
|
104
|
+
proxyCountry?: string;
|
|
105
|
+
/** When true, after the primary crawl auto-enqueue one CRAWL job per credentialProfile. */
|
|
106
|
+
crawlAllRoles?: boolean;
|
|
107
|
+
/** Decrypted credential profiles used when crawlAllRoles is true. */
|
|
108
|
+
credentialProfiles?: Array<{
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
username: string;
|
|
112
|
+
password: string;
|
|
113
|
+
}>;
|
|
114
|
+
/** Set on role-crawl sub-jobs — profile name tagged on discovered screens via roleAccess column. */
|
|
115
|
+
roleProfileName?: string;
|
|
70
116
|
}
|
|
71
117
|
type ClaimedJob = {
|
|
72
118
|
id: string;
|
|
@@ -95,6 +141,13 @@ export declare function requeueStaleRunningJobs(): Promise<void>;
|
|
|
95
141
|
* Safe to call from multiple replicas simultaneously (each uses its own WORKER_ID).
|
|
96
142
|
*/
|
|
97
143
|
export declare function requeueOrphanedJobs(): Promise<void>;
|
|
144
|
+
/** Dispatches a claimed job to its type's processor. Last-resort safety net — every
|
|
145
|
+
* process*Job function already catches and records its own failures. */
|
|
146
|
+
export declare function dispatchClaimedJob(job: ClaimedJob): Promise<void>;
|
|
147
|
+
/** Re-promotes FAILED jobs to QUEUED if they are eligible for retry based on per-type limits. */
|
|
148
|
+
export declare function retryEligibleFailedJobs(): Promise<void>;
|
|
149
|
+
/** Promotes FAILED jobs to DLQ if they exceed their per-type retry limits. */
|
|
150
|
+
export declare function promoteToDeadLetterQueue(): Promise<void>;
|
|
98
151
|
/**
|
|
99
152
|
* Starts the durable job worker poll loop. This is what makes job processing
|
|
100
153
|
* survive a restart — enqueueJob()'s immediate same-process claim attempt is
|
package/types/sqs-queue.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
export declare function sendJobToSqs(jobId: string
|
|
1
|
+
export declare function sendJobToSqs(jobId: string): Promise<void>;
|
|
2
|
+
/** Returns the approximate number of messages in the SQS DLQ. */
|
|
3
|
+
export declare function getDlqStats(): Promise<{
|
|
4
|
+
approximateMessageCount: number;
|
|
5
|
+
dlqUrl: string | null;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Redrive up to `maxMessages` messages from the SQS DLQ back to the main queue.
|
|
9
|
+
* Each message is received from the DLQ, re-sent to the main queue, then deleted
|
|
10
|
+
* from the DLQ. Returns the number of messages successfully redriven.
|
|
11
|
+
*/
|
|
12
|
+
export declare function redriveFromDlq(maxMessages?: number): Promise<number>;
|
|
2
13
|
export declare function startSqsWorker(): Promise<void>;
|