@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.
Files changed (40) hide show
  1. package/dist/crawler.js +173 -28
  2. package/dist/crawler.js.map +2 -2
  3. package/dist/extractors/branding-extractor.js +104 -0
  4. package/dist/extractors/branding-extractor.js.map +7 -0
  5. package/dist/extractors/summary-extractor.js +36 -0
  6. package/dist/extractors/summary-extractor.js.map +7 -0
  7. package/dist/frontier.js +20 -2
  8. package/dist/frontier.js.map +2 -2
  9. package/dist/incremental-diff.js +76 -0
  10. package/dist/incremental-diff.js.map +2 -2
  11. package/dist/index.js +3 -1
  12. package/dist/index.js.map +2 -2
  13. package/dist/interactive-probe.js +108 -17
  14. package/dist/interactive-probe.js.map +2 -2
  15. package/dist/job-queue.js +408 -61
  16. package/dist/job-queue.js.map +2 -2
  17. package/dist/sqs-queue.js +78 -10
  18. package/dist/sqs-queue.js.map +2 -2
  19. package/dist/worker.js +7 -7
  20. package/dist/worker.js.map +2 -2
  21. package/package.json +1 -1
  22. package/src/crawler.ts +208 -30
  23. package/src/extractors/branding-extractor.ts +159 -0
  24. package/src/extractors/summary-extractor.ts +49 -0
  25. package/src/frontier.ts +31 -1
  26. package/src/incremental-diff.ts +138 -0
  27. package/src/index.ts +1 -1
  28. package/src/interactive-probe.ts +129 -20
  29. package/src/job-queue.ts +489 -63
  30. package/src/sqs-queue.ts +92 -12
  31. package/src/worker.ts +13 -11
  32. package/types/crawler.d.ts +20 -0
  33. package/types/extractors/branding-extractor.d.ts +46 -0
  34. package/types/extractors/summary-extractor.d.ts +10 -0
  35. package/types/frontier.d.ts +9 -1
  36. package/types/incremental-diff.d.ts +57 -0
  37. package/types/index.d.ts +1 -1
  38. package/types/interactive-probe.d.ts +5 -1
  39. package/types/job-queue.d.ts +53 -0
  40. package/types/sqs-queue.d.ts +12 -1
package/dist/sqs-queue.js CHANGED
@@ -1,17 +1,80 @@
1
- import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs";
1
+ import {
2
+ SQSClient,
3
+ SendMessageCommand,
4
+ ReceiveMessageCommand,
5
+ DeleteMessageCommand,
6
+ GetQueueAttributesCommand
7
+ } from "@aws-sdk/client-sqs";
2
8
  const sqs = new SQSClient({ region: process.env.AWS_REGION ?? "us-east-1" });
3
- async function sendJobToSqs(jobId, tenantId, config) {
4
- const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
5
- if (!queueUrl) throw new Error("SQS_CRAWL_QUEUE_URL not configured");
9
+ function getQueueUrl() {
10
+ const url = process.env.SQS_QUEUE_URL ?? process.env.SQS_CRAWL_QUEUE_URL;
11
+ if (!url) throw new Error("SQS_QUEUE_URL not configured");
12
+ return url;
13
+ }
14
+ function getDlqUrl() {
15
+ return process.env.SQS_DLQ_URL ?? null;
16
+ }
17
+ async function sendJobToSqs(jobId) {
6
18
  await sqs.send(new SendMessageCommand({
7
- QueueUrl: queueUrl,
8
- MessageBody: JSON.stringify({ jobId, tenantId, config })
19
+ QueueUrl: getQueueUrl(),
20
+ MessageBody: JSON.stringify({ jobId })
9
21
  }));
10
22
  }
23
+ async function getDlqStats() {
24
+ const dlqUrl = getDlqUrl();
25
+ if (!dlqUrl) return { approximateMessageCount: 0, dlqUrl: null };
26
+ try {
27
+ const { Attributes = {} } = await sqs.send(new GetQueueAttributesCommand({
28
+ QueueUrl: dlqUrl,
29
+ AttributeNames: ["ApproximateNumberOfMessages"]
30
+ }));
31
+ return {
32
+ approximateMessageCount: parseInt(Attributes.ApproximateNumberOfMessages ?? "0", 10),
33
+ dlqUrl
34
+ };
35
+ } catch (err) {
36
+ console.error("[sqs-dlq] getDlqStats error:", err.message);
37
+ return { approximateMessageCount: 0, dlqUrl };
38
+ }
39
+ }
40
+ async function redriveFromDlq(maxMessages = 10) {
41
+ const dlqUrl = getDlqUrl();
42
+ if (!dlqUrl) {
43
+ console.warn("[sqs-dlq] SQS_DLQ_URL not configured \u2014 cannot redrive");
44
+ return 0;
45
+ }
46
+ const mainUrl = getQueueUrl();
47
+ let totalRedriven = 0;
48
+ while (totalRedriven < maxMessages) {
49
+ const batch = Math.min(10, maxMessages - totalRedriven);
50
+ const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({
51
+ QueueUrl: dlqUrl,
52
+ MaxNumberOfMessages: batch,
53
+ WaitTimeSeconds: 1
54
+ }));
55
+ if (Messages.length === 0) break;
56
+ for (const msg of Messages) {
57
+ try {
58
+ await sqs.send(new SendMessageCommand({
59
+ QueueUrl: mainUrl,
60
+ MessageBody: msg.Body
61
+ }));
62
+ await sqs.send(new DeleteMessageCommand({
63
+ QueueUrl: dlqUrl,
64
+ ReceiptHandle: msg.ReceiptHandle
65
+ }));
66
+ totalRedriven++;
67
+ console.log("[sqs-dlq] redriven message:", msg.MessageId);
68
+ } catch (err) {
69
+ console.error("[sqs-dlq] redrive error for message", msg.MessageId, ":", err.message);
70
+ }
71
+ }
72
+ }
73
+ return totalRedriven;
74
+ }
11
75
  async function startSqsWorker() {
12
- const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
13
- if (!queueUrl) throw new Error("SQS_CRAWL_QUEUE_URL not configured");
14
- const { processJob } = await import("./job-queue.js");
76
+ const queueUrl = getQueueUrl();
77
+ const { claimSpecificJob, dispatchClaimedJob } = await import("./job-queue.js");
15
78
  console.log("[sqs-worker] starting, queue:", queueUrl);
16
79
  while (true) {
17
80
  try {
@@ -25,7 +88,10 @@ async function startSqsWorker() {
25
88
  try {
26
89
  const data = JSON.parse(msg.Body);
27
90
  jobId = data.jobId;
28
- await processJob(data.jobId, data.config);
91
+ const claimed = await claimSpecificJob(jobId);
92
+ if (claimed) {
93
+ await dispatchClaimedJob(claimed);
94
+ }
29
95
  await sqs.send(new DeleteMessageCommand({
30
96
  QueueUrl: queueUrl,
31
97
  ReceiptHandle: msg.ReceiptHandle
@@ -41,6 +107,8 @@ async function startSqsWorker() {
41
107
  }
42
108
  }
43
109
  export {
110
+ getDlqStats,
111
+ redriveFromDlq,
44
112
  sendJobToSqs,
45
113
  startSqsWorker
46
114
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/sqs-queue.ts"],
4
- "sourcesContent": ["import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';\n\nconst sqs = new SQSClient({ region: process.env.AWS_REGION ?? 'us-east-1' });\n\nexport async function sendJobToSqs(jobId: string, tenantId: string, config: any): Promise<void> {\n const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;\n if (!queueUrl) throw new Error('SQS_CRAWL_QUEUE_URL not configured');\n await sqs.send(new SendMessageCommand({\n QueueUrl: queueUrl,\n MessageBody: JSON.stringify({ jobId, tenantId, config }),\n }));\n}\n\nexport async function startSqsWorker(): Promise<void> {\n const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;\n if (!queueUrl) throw new Error('SQS_CRAWL_QUEUE_URL not configured');\n\n const { processJob } = await import('./job-queue.js');\n console.log('[sqs-worker] starting, queue:', queueUrl);\n\n while (true) {\n try {\n const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({\n QueueUrl: queueUrl,\n WaitTimeSeconds: 20,\n MaxNumberOfMessages: 2,\n }));\n\n await Promise.all(Messages.map(async (msg) => {\n let jobId = '?';\n try {\n const data = JSON.parse(msg.Body!);\n jobId = data.jobId;\n await processJob(data.jobId, data.config);\n await sqs.send(new DeleteMessageCommand({\n QueueUrl: queueUrl,\n ReceiptHandle: msg.ReceiptHandle!,\n }));\n } catch (err: any) {\n console.error(`[sqs-worker] job ${jobId} failed \u2014 leaving for SQS redrive:`, err.message);\n // Do NOT delete \u2014 SQS redrive policy + DLQ handles retry\n }\n }));\n } catch (err: any) {\n console.error('[sqs-worker] receive error:', err.message);\n await new Promise((r) => setTimeout(r, 2000));\n }\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,WAAW,oBAAoB,uBAAuB,4BAA4B;AAE3F,MAAM,MAAM,IAAI,UAAU,EAAE,QAAQ,QAAQ,IAAI,cAAc,YAAY,CAAC;AAE3E,eAAsB,aAAa,OAAe,UAAkB,QAA4B;AAC9F,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,oCAAoC;AACnE,QAAM,IAAI,KAAK,IAAI,mBAAmB;AAAA,IACpC,UAAU;AAAA,IACV,aAAa,KAAK,UAAU,EAAE,OAAO,UAAU,OAAO,CAAC;AAAA,EACzD,CAAC,CAAC;AACJ;AAEA,eAAsB,iBAAgC;AACpD,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,oCAAoC;AAEnE,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,gBAAgB;AACpD,UAAQ,IAAI,iCAAiC,QAAQ;AAErD,SAAO,MAAM;AACX,QAAI;AACF,YAAM,EAAE,WAAW,CAAC,EAAE,IAAI,MAAM,IAAI,KAAK,IAAI,sBAAsB;AAAA,QACjE,UAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,qBAAqB;AAAA,MACvB,CAAC,CAAC;AAEF,YAAM,QAAQ,IAAI,SAAS,IAAI,OAAO,QAAQ;AAC5C,YAAI,QAAQ;AACZ,YAAI;AACF,gBAAM,OAAO,KAAK,MAAM,IAAI,IAAK;AACjC,kBAAQ,KAAK;AACb,gBAAM,WAAW,KAAK,OAAO,KAAK,MAAM;AACxC,gBAAM,IAAI,KAAK,IAAI,qBAAqB;AAAA,YACtC,UAAU;AAAA,YACV,eAAe,IAAI;AAAA,UACrB,CAAC,CAAC;AAAA,QACJ,SAAS,KAAU;AACjB,kBAAQ,MAAM,oBAAoB,KAAK,2CAAsC,IAAI,OAAO;AAAA,QAE1F;AAAA,MACF,CAAC,CAAC;AAAA,IACJ,SAAS,KAAU;AACjB,cAAQ,MAAM,+BAA+B,IAAI,OAAO;AACxD,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAI,CAAC;AAAA,IAC9C;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import {\n SQSClient,\n SendMessageCommand,\n ReceiveMessageCommand,\n DeleteMessageCommand,\n GetQueueAttributesCommand,\n} from '@aws-sdk/client-sqs';\n\nconst sqs = new SQSClient({ region: process.env.AWS_REGION ?? 'us-east-1' });\n\nfunction getQueueUrl(): string {\n const url = process.env.SQS_QUEUE_URL ?? process.env.SQS_CRAWL_QUEUE_URL;\n if (!url) throw new Error('SQS_QUEUE_URL not configured');\n return url;\n}\n\nfunction getDlqUrl(): string | null {\n return process.env.SQS_DLQ_URL ?? null;\n}\n\nexport async function sendJobToSqs(jobId: string): Promise<void> {\n await sqs.send(new SendMessageCommand({\n QueueUrl: getQueueUrl(),\n MessageBody: JSON.stringify({ jobId }),\n }));\n}\n\n/** Returns the approximate number of messages in the SQS DLQ. */\nexport async function getDlqStats(): Promise<{ approximateMessageCount: number; dlqUrl: string | null }> {\n const dlqUrl = getDlqUrl();\n if (!dlqUrl) return { approximateMessageCount: 0, dlqUrl: null };\n try {\n const { Attributes = {} } = await sqs.send(new GetQueueAttributesCommand({\n QueueUrl: dlqUrl,\n AttributeNames: ['ApproximateNumberOfMessages'],\n }));\n return {\n approximateMessageCount: parseInt(Attributes.ApproximateNumberOfMessages ?? '0', 10),\n dlqUrl,\n };\n } catch (err: any) {\n console.error('[sqs-dlq] getDlqStats error:', err.message);\n return { approximateMessageCount: 0, dlqUrl };\n }\n}\n\n/**\n * Redrive up to `maxMessages` messages from the SQS DLQ back to the main queue.\n * Each message is received from the DLQ, re-sent to the main queue, then deleted\n * from the DLQ. Returns the number of messages successfully redriven.\n */\nexport async function redriveFromDlq(maxMessages = 10): Promise<number> {\n const dlqUrl = getDlqUrl();\n if (!dlqUrl) {\n console.warn('[sqs-dlq] SQS_DLQ_URL not configured \u2014 cannot redrive');\n return 0;\n }\n const mainUrl = getQueueUrl();\n let totalRedriven = 0;\n\n while (totalRedriven < maxMessages) {\n const batch = Math.min(10, maxMessages - totalRedriven);\n const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({\n QueueUrl: dlqUrl,\n MaxNumberOfMessages: batch,\n WaitTimeSeconds: 1,\n }));\n if (Messages.length === 0) break;\n\n for (const msg of Messages) {\n try {\n await sqs.send(new SendMessageCommand({\n QueueUrl: mainUrl,\n MessageBody: msg.Body!,\n }));\n await sqs.send(new DeleteMessageCommand({\n QueueUrl: dlqUrl,\n ReceiptHandle: msg.ReceiptHandle!,\n }));\n totalRedriven++;\n console.log('[sqs-dlq] redriven message:', msg.MessageId);\n } catch (err: any) {\n console.error('[sqs-dlq] redrive error for message', msg.MessageId, ':', err.message);\n }\n }\n }\n\n return totalRedriven;\n}\n\nexport async function startSqsWorker(): Promise<void> {\n const queueUrl = getQueueUrl();\n const { claimSpecificJob, dispatchClaimedJob } = await import('./job-queue.js');\n console.log('[sqs-worker] starting, queue:', queueUrl);\n\n while (true) {\n try {\n const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({\n QueueUrl: queueUrl,\n WaitTimeSeconds: 20,\n MaxNumberOfMessages: 2,\n }));\n\n await Promise.all(Messages.map(async (msg) => {\n let jobId = '?';\n try {\n const data = JSON.parse(msg.Body!);\n jobId = data.jobId;\n // Atomically move QUEUED \u2192 RUNNING; returns null if another worker beat us.\n const claimed = await claimSpecificJob(jobId);\n if (claimed) {\n await dispatchClaimedJob(claimed);\n }\n // Delete whether we ran it or another worker did \u2014 message is handled either way.\n await sqs.send(new DeleteMessageCommand({\n QueueUrl: queueUrl,\n ReceiptHandle: msg.ReceiptHandle!,\n }));\n } catch (err: any) {\n console.error(`[sqs-worker] job ${jobId} failed \u2014 leaving for SQS redrive:`, err.message);\n // Do NOT delete \u2014 SQS visibility timeout expires and DLQ handles retry.\n }\n }));\n } catch (err: any) {\n console.error('[sqs-worker] receive error:', err.message);\n await new Promise((r) => setTimeout(r, 2000));\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/worker.js CHANGED
@@ -2,6 +2,13 @@ import { startJobWorkerLoop, processDataRetentionPurge } from "./job-queue.js";
2
2
  import { logger } from "./logger.js";
3
3
  async function main() {
4
4
  logger.info("[worker] starting standalone crawler worker process");
5
+ if (process.env.JOB_QUEUE_BACKEND === "sqs") {
6
+ const { startSqsWorker } = await import("./sqs-queue.js");
7
+ startSqsWorker().catch((err) => {
8
+ logger.error({ err: String(err) }, "[worker] SQS worker crashed");
9
+ process.exit(1);
10
+ });
11
+ }
5
12
  startJobWorkerLoop();
6
13
  const runPurge = () => {
7
14
  processDataRetentionPurge().catch((err) => {
@@ -10,13 +17,6 @@ async function main() {
10
17
  };
11
18
  runPurge();
12
19
  setInterval(runPurge, 24 * 60 * 60 * 1e3);
13
- if (process.env.JOB_QUEUE_BACKEND === "sqs") {
14
- const { startSqsWorker } = await import("./sqs-queue.js");
15
- startSqsWorker().catch((err) => {
16
- logger.error({ err: String(err) }, "[worker] SQS worker crashed");
17
- process.exit(1);
18
- });
19
- }
20
20
  }
21
21
  process.on("SIGTERM", () => {
22
22
  logger.info("[worker] SIGTERM received, exiting");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/worker.ts"],
4
- "sourcesContent": ["/**\n * Standalone worker process entrypoint for a dedicated crawler/worker container,\n * separate from the API server (see infrastructure/ecs-task-definitions/crawler.json).\n *\n * index.ts is a side-effect-free library barrel imported by @detiq/api \u2014 it must NOT\n * start any background loop itself, or importing a single helper from @detiq/crawler\n * would silently spin up a second worker inside the API process too. This file is\n * the actual entrypoint that keeps a dedicated worker container's process alive and\n * consuming jobs: `node dist/worker.js`.\n */\nimport { startJobWorkerLoop, processDataRetentionPurge } from './job-queue.js';\nimport { logger } from './logger.js';\n\nasync function main() {\n logger.info('[worker] starting standalone crawler worker process');\n\n // Durable DB-backed queue \u2014 claims every non-CRAWL job type, and CRAWL too when\n // JOB_QUEUE_BACKEND isn't 'sqs'. Safe to run in many worker replicas at once.\n startJobWorkerLoop();\n\n // Daily data-retention purge: runs immediately on startup then every 24 h.\n // Only runs in the dedicated worker container (this file), not in the API\n // process, so there is exactly one purge sweep regardless of API replica count.\n const runPurge = () => {\n processDataRetentionPurge().catch((err) => {\n logger.error({ err: String(err) }, '[worker] data-retention purge failed');\n });\n };\n runPurge();\n setInterval(runPurge, 24 * 60 * 60 * 1000);\n\n // Optional: also consume the SQS transport for horizontally-scaled CRAWL fleets.\n if (process.env.JOB_QUEUE_BACKEND === 'sqs') {\n const { startSqsWorker } = await import('./sqs-queue.js');\n startSqsWorker().catch((err) => {\n logger.error({ err: String(err) }, '[worker] SQS worker crashed');\n process.exit(1);\n });\n }\n}\n\nprocess.on('SIGTERM', () => { logger.info('[worker] SIGTERM received, exiting'); process.exit(0); });\nprocess.on('SIGINT', () => { logger.info('[worker] SIGINT received, exiting'); process.exit(0); });\n\nmain().catch((err) => {\n logger.error({ err: String(err) }, '[worker] fatal startup error');\n process.exit(1);\n});\n"],
5
- "mappings": "AAUA,SAAS,oBAAoB,iCAAiC;AAC9D,SAAS,cAAc;AAEvB,eAAe,OAAO;AACpB,SAAO,KAAK,qDAAqD;AAIjE,qBAAmB;AAKnB,QAAM,WAAW,MAAM;AACrB,8BAA0B,EAAE,MAAM,CAAC,QAAQ;AACzC,aAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,sCAAsC;AAAA,IAC3E,CAAC;AAAA,EACH;AACA,WAAS;AACT,cAAY,UAAU,KAAK,KAAK,KAAK,GAAI;AAGzC,MAAI,QAAQ,IAAI,sBAAsB,OAAO;AAC3C,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,gBAAgB;AACxD,mBAAe,EAAE,MAAM,CAAC,QAAQ;AAC9B,aAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,6BAA6B;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH;AACF;AAEA,QAAQ,GAAG,WAAW,MAAM;AAAE,SAAO,KAAK,oCAAoC;AAAG,UAAQ,KAAK,CAAC;AAAG,CAAC;AACnG,QAAQ,GAAG,UAAU,MAAM;AAAE,SAAO,KAAK,mCAAmC;AAAG,UAAQ,KAAK,CAAC;AAAG,CAAC;AAEjG,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,SAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,8BAA8B;AACjE,UAAQ,KAAK,CAAC;AAChB,CAAC;",
4
+ "sourcesContent": ["/**\n * Standalone worker process entrypoint for a dedicated crawler/worker container,\n * separate from the API server (see infrastructure/ecs-task-definitions/crawler.json).\n *\n * index.ts is a side-effect-free library barrel imported by @detiq/api \u2014 it must NOT\n * start any background loop itself, or importing a single helper from @detiq/crawler\n * would silently spin up a second worker inside the API process too. This file is\n * the actual entrypoint that keeps a dedicated worker container's process alive and\n * consuming jobs: `node dist/worker.js`.\n */\nimport { startJobWorkerLoop, processDataRetentionPurge } from './job-queue.js';\nimport { logger } from './logger.js';\n\nasync function main() {\n logger.info('[worker] starting standalone crawler worker process');\n\n if (process.env.JOB_QUEUE_BACKEND === 'sqs') {\n // SQS mode: messages arrive via long-poll; claimSpecificJob atomically moves\n // QUEUED\u2192RUNNING before dispatch, so multiple worker replicas are safe.\n // DB poll loop also runs as a safety net for jobs whose SQS message was lost.\n const { startSqsWorker } = await import('./sqs-queue.js');\n startSqsWorker().catch((err) => {\n logger.error({ err: String(err) }, '[worker] SQS worker crashed');\n process.exit(1);\n });\n }\n\n // DB-backed poll loop \u2014 primary in non-SQS mode, safety net in SQS mode.\n // Claims all job types. Safe to run in many worker replicas (FOR UPDATE SKIP LOCKED).\n startJobWorkerLoop();\n\n // Daily data-retention purge: runs immediately on startup then every 24 h.\n // Only runs in the dedicated worker container (this file), not in the API\n // process, so there is exactly one purge sweep regardless of API replica count.\n const runPurge = () => {\n processDataRetentionPurge().catch((err) => {\n logger.error({ err: String(err) }, '[worker] data-retention purge failed');\n });\n };\n runPurge();\n setInterval(runPurge, 24 * 60 * 60 * 1000);\n}\n\nprocess.on('SIGTERM', () => { logger.info('[worker] SIGTERM received, exiting'); process.exit(0); });\nprocess.on('SIGINT', () => { logger.info('[worker] SIGINT received, exiting'); process.exit(0); });\n\nmain().catch((err) => {\n logger.error({ err: String(err) }, '[worker] fatal startup error');\n process.exit(1);\n});\n"],
5
+ "mappings": "AAUA,SAAS,oBAAoB,iCAAiC;AAC9D,SAAS,cAAc;AAEvB,eAAe,OAAO;AACpB,SAAO,KAAK,qDAAqD;AAEjE,MAAI,QAAQ,IAAI,sBAAsB,OAAO;AAI3C,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,gBAAgB;AACxD,mBAAe,EAAE,MAAM,CAAC,QAAQ;AAC9B,aAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,6BAA6B;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH;AAIA,qBAAmB;AAKnB,QAAM,WAAW,MAAM;AACrB,8BAA0B,EAAE,MAAM,CAAC,QAAQ;AACzC,aAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,sCAAsC;AAAA,IAC3E,CAAC;AAAA,EACH;AACA,WAAS;AACT,cAAY,UAAU,KAAK,KAAK,KAAK,GAAI;AAC3C;AAEA,QAAQ,GAAG,WAAW,MAAM;AAAE,SAAO,KAAK,oCAAoC;AAAG,UAAQ,KAAK,CAAC;AAAG,CAAC;AACnG,QAAQ,GAAG,UAAU,MAAM;AAAE,SAAO,KAAK,mCAAmC;AAAG,UAAQ,KAAK,CAAC;AAAG,CAAC;AAEjG,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,SAAO,MAAM,EAAE,KAAK,OAAO,GAAG,EAAE,GAAG,8BAA8B;AACjE,UAAQ,KAAK,CAAC;AAChB,CAAC;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@detiq/crawler",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",