@detiq/crawler 1.1.19 → 1.1.21
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/auth.js +12 -2
- package/dist/auth.js.map +2 -2
- package/dist/crawler.js +395 -155
- package/dist/crawler.js.map +3 -3
- package/dist/job-queue.js +46 -3
- package/dist/job-queue.js.map +2 -2
- package/dist/native-crawler.js +6 -6
- package/dist/native-crawler.js.map +2 -2
- package/package.json +1 -1
- package/src/auth.ts +13 -1
- package/src/crawler.ts +317 -60
- package/src/job-queue.ts +30 -3
- package/src/native-crawler.ts +6 -4
- package/types/crawler.d.ts +4 -0
- package/types/job-queue.d.ts +3 -1
package/dist/job-queue.js
CHANGED
|
@@ -113,6 +113,19 @@ async function claimNextQueuedJob() {
|
|
|
113
113
|
return rows[0] ?? null;
|
|
114
114
|
}
|
|
115
115
|
async function requeueStaleRunningJobs() {
|
|
116
|
+
const failedRows = await prismaAdmin.$queryRaw`
|
|
117
|
+
UPDATE "PlatformJob"
|
|
118
|
+
SET status = 'FAILED',
|
|
119
|
+
"finishedAt" = NOW(),
|
|
120
|
+
error = 'API process was restarted while this job was running — please retry from the UI.'
|
|
121
|
+
WHERE status = 'RUNNING'
|
|
122
|
+
AND type IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')
|
|
123
|
+
AND COALESCE("heartbeatAt", "startedAt") < NOW() - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')
|
|
124
|
+
RETURNING id
|
|
125
|
+
`;
|
|
126
|
+
if (failedRows.length > 0) {
|
|
127
|
+
logger.warn({ jobIds: failedRows.map((r) => r.id), staleTimeoutMinutes: STALE_JOB_TIMEOUT_MINUTES }, "[job-worker] failed stale API-in-process job(s) \u2014 API restarted while running");
|
|
128
|
+
}
|
|
116
129
|
const rows = await prismaAdmin.$queryRaw`
|
|
117
130
|
UPDATE "PlatformJob"
|
|
118
131
|
SET status = 'QUEUED',
|
|
@@ -124,6 +137,7 @@ async function requeueStaleRunningJobs() {
|
|
|
124
137
|
true
|
|
125
138
|
)
|
|
126
139
|
WHERE status = 'RUNNING'
|
|
140
|
+
AND type NOT IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')
|
|
127
141
|
AND COALESCE("heartbeatAt", "startedAt") < NOW() - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')
|
|
128
142
|
RETURNING id
|
|
129
143
|
`;
|
|
@@ -469,8 +483,27 @@ async function processJob(jobId, config, opts) {
|
|
|
469
483
|
}, onProgress);
|
|
470
484
|
jobCheckpoints.delete(jobId);
|
|
471
485
|
const pendingUrls = result?.pendingUrls ?? [];
|
|
486
|
+
let nextJobId;
|
|
472
487
|
if (pendingUrls.length > 0) {
|
|
473
|
-
|
|
488
|
+
const visitedForSkip = result.visitedUrls ?? (result.discovered ?? []).filter((d) => !!d.screenId && !!d.url).map((d) => {
|
|
489
|
+
try {
|
|
490
|
+
const _u = new URL(d.url);
|
|
491
|
+
_u.hash = "";
|
|
492
|
+
return _u.href;
|
|
493
|
+
} catch {
|
|
494
|
+
return d.url;
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
const reason = result.wasTimeout ? "timeout" : "segment-limit";
|
|
498
|
+
logger.info({ pendingUrls: pendingUrls.length, visited: visitedForSkip.length, reason }, "[crawl] auto-enqueuing continuation job");
|
|
499
|
+
nextJobId = await enqueueJob("CRAWL", {
|
|
500
|
+
...config,
|
|
501
|
+
startUrls: pendingUrls,
|
|
502
|
+
skipUrls: [...config.skipUrls ?? [], ...visitedForSkip]
|
|
503
|
+
}).catch((autoErr) => {
|
|
504
|
+
logger.warn({ err: autoErr?.message }, "[crawl] auto-segment enqueue failed (non-fatal)");
|
|
505
|
+
return void 0;
|
|
506
|
+
});
|
|
474
507
|
}
|
|
475
508
|
await withTenant(
|
|
476
509
|
tenantId,
|
|
@@ -478,7 +511,7 @@ async function processJob(jobId, config, opts) {
|
|
|
478
511
|
where: { id: jobId },
|
|
479
512
|
data: {
|
|
480
513
|
status: "DONE",
|
|
481
|
-
result: { ...result },
|
|
514
|
+
result: { ...result, ...nextJobId ? { nextJobId } : {} },
|
|
482
515
|
progress: 100,
|
|
483
516
|
finishedAt: /* @__PURE__ */ new Date()
|
|
484
517
|
}
|
|
@@ -586,7 +619,7 @@ async function processJob(jobId, config, opts) {
|
|
|
586
619
|
try {
|
|
587
620
|
const key = buildArtifactKey(tenantId, projectId, "dom-snapshots", `${screen.id}.json`, tenantStorageCfg.prefix);
|
|
588
621
|
const cloudUrl = await uploadFileToTenantStorage(tenantStorageCfg, domPath, key, "application/json");
|
|
589
|
-
await AppBrain.updateScreenDomSnapshot(tenantId, screen.id, cloudUrl);
|
|
622
|
+
await AppBrain.updateScreenDomSnapshot(tenantId, screen.id, domPath, cloudUrl);
|
|
590
623
|
domUploaded++;
|
|
591
624
|
} catch (err) {
|
|
592
625
|
logger.warn({ screenId: screen.id, err }, "Failed to upload DOM snapshot to cloud storage");
|
|
@@ -647,8 +680,18 @@ async function processJob(jobId, config, opts) {
|
|
|
647
680
|
clearInterval(heartbeatTimer);
|
|
648
681
|
runningControllers.delete(jobId);
|
|
649
682
|
const aborted = String(err).includes("CRAWL_ABORTED");
|
|
683
|
+
const authFailure = !aborted && (String(err).includes("AUTH_FAILURE") || String(err).includes("SESSION_EXPIRED"));
|
|
650
684
|
const checkpoint = jobCheckpoints.get(jobId);
|
|
651
685
|
jobCheckpoints.delete(jobId);
|
|
686
|
+
if (authFailure && (checkpoint?.pendingQueue?.length ?? 0) > 0) {
|
|
687
|
+
const pendingAfterAuth = checkpoint.pendingQueue;
|
|
688
|
+
logger.info({ pending: pendingAfterAuth.length }, "[crawl] auth failure mid-crawl \u2014 auto-enqueuing continuation with pending URLs");
|
|
689
|
+
enqueueJob("CRAWL", {
|
|
690
|
+
...config,
|
|
691
|
+
startUrls: pendingAfterAuth,
|
|
692
|
+
skipUrls: [...config.skipUrls ?? [], ...checkpoint.visitedUrls ?? []]
|
|
693
|
+
}).catch((enqErr) => logger.warn({ err: enqErr?.message }, "[crawl] auth-failure auto-segment enqueue failed (non-fatal)"));
|
|
694
|
+
}
|
|
652
695
|
await withTenant(
|
|
653
696
|
tenantId,
|
|
654
697
|
(tx) => tx.platformJob.update({
|