((resolve) => setTimeout(() => resolve(), 75));
}
}
@@ -70,7 +84,10 @@ export async function logJob(job: FirecrawlJob, force: boolean = false) {
.from("firecrawl_jobs")
.insert([jobColumn]);
if (error) {
- logger.error(`Error logging job: ${error.message}`, { error, scrapeId: job.job_id });
+ logger.error(`Error logging job: ${error.message}`, {
+ error,
+ scrapeId: job.job_id
+ });
} else {
logger.debug("Job logged successfully!", { scrapeId: job.job_id });
}
@@ -80,7 +97,7 @@ export async function logJob(job: FirecrawlJob, force: boolean = false) {
let phLog = {
distinctId: "from-api", //* To identify this on the group level, setting distinctid to a static string per posthog docs: https://posthog.com/docs/product-analytics/group-analytics#advanced-server-side-only-capturing-group-events-without-a-user
...(job.team_id !== "preview" && {
- groups: { team: job.team_id },
+ groups: { team: job.team_id }
}), //* Identifying event on this team
event: "job-logged",
properties: {
@@ -95,14 +112,13 @@ export async function logJob(job: FirecrawlJob, force: boolean = false) {
page_options: job.scrapeOptions,
origin: job.origin,
num_tokens: job.num_tokens,
- retry: job.retry,
- },
+ retry: job.retry
+ }
};
- if(job.mode !== "single_urls") {
+ if (job.mode !== "single_urls") {
posthog.capture(phLog);
}
}
-
} catch (error) {
logger.error(`Error logging job: ${error.message}`);
}
diff --git a/apps/api/src/services/logging/scrape_log.ts b/apps/api/src/services/logging/scrape_log.ts
index 441b3894..3ccaf777 100644
--- a/apps/api/src/services/logging/scrape_log.ts
+++ b/apps/api/src/services/logging/scrape_log.ts
@@ -10,7 +10,7 @@ export async function logScrape(
scrapeLog: ScrapeLog,
pageOptions?: PageOptions
) {
- const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === 'true';
+ const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === "true";
if (!useDbAuthentication) {
logger.debug("Skipping logging scrape to Supabase");
return;
@@ -42,8 +42,8 @@ export async function logScrape(
date_added: new Date().toISOString(),
html: "Removed to save db space",
ipv4_support: scrapeLog.ipv4_support,
- ipv6_support: scrapeLog.ipv6_support,
- },
+ ipv6_support: scrapeLog.ipv6_support
+ }
]);
if (error) {
diff --git a/apps/api/src/services/notification/email_notification.ts b/apps/api/src/services/notification/email_notification.ts
index 982d402e..22c23865 100644
--- a/apps/api/src/services/notification/email_notification.ts
+++ b/apps/api/src/services/notification/email_notification.ts
@@ -14,25 +14,25 @@ const emailTemplates: Record<
> = {
[NotificationType.APPROACHING_LIMIT]: {
subject: "You've used 80% of your credit limit - Firecrawl",
- html: "Hey there,
You are approaching your credit limit for this billing period. Your usage right now is around 80% of your total credit limit. Consider upgrading your plan to avoid hitting the limit. Check out our pricing page for more info.
Thanks,
Firecrawl Team
",
+ html: "Hey there,
You are approaching your credit limit for this billing period. Your usage right now is around 80% of your total credit limit. Consider upgrading your plan to avoid hitting the limit. Check out our pricing page for more info.
Thanks,
Firecrawl Team
"
},
[NotificationType.LIMIT_REACHED]: {
subject:
"Credit Limit Reached! Take action now to resume usage - Firecrawl",
- html: "Hey there,
You have reached your credit limit for this billing period. To resume usage, please upgrade your plan. Check out our pricing page for more info.
Thanks,
Firecrawl Team
",
+ html: "Hey there,
You have reached your credit limit for this billing period. To resume usage, please upgrade your plan. Check out our pricing page for more info.
Thanks,
Firecrawl Team
"
},
[NotificationType.RATE_LIMIT_REACHED]: {
subject: "Rate Limit Reached - Firecrawl",
- html: "Hey there,
You've hit one of the Firecrawl endpoint's rate limit! Take a breather and try again in a few moments. If you need higher rate limits, consider upgrading your plan. Check out our pricing page for more info.
If you have any questions, feel free to reach out to us at help@firecrawl.com
Thanks,
Firecrawl Team
Ps. this email is only sent once every 7 days if you reach a rate limit.",
+ html: "Hey there,
You've hit one of the Firecrawl endpoint's rate limit! Take a breather and try again in a few moments. If you need higher rate limits, consider upgrading your plan. Check out our pricing page for more info.
If you have any questions, feel free to reach out to us at help@firecrawl.com
Thanks,
Firecrawl Team
Ps. this email is only sent once every 7 days if you reach a rate limit."
},
[NotificationType.AUTO_RECHARGE_SUCCESS]: {
subject: "Auto recharge successful - Firecrawl",
- html: "Hey there,
Your account was successfully recharged with 1000 credits because your remaining credits were below the threshold. Consider upgrading your plan at firecrawl.dev/pricing to avoid hitting the limit.
Thanks,
Firecrawl Team
",
+ html: "Hey there,
Your account was successfully recharged with 1000 credits because your remaining credits were below the threshold. Consider upgrading your plan at firecrawl.dev/pricing to avoid hitting the limit.
Thanks,
Firecrawl Team
"
},
[NotificationType.AUTO_RECHARGE_FAILED]: {
subject: "Auto recharge failed - Firecrawl",
- html: "Hey there,
Your auto recharge failed. Please try again manually. If the issue persists, please reach out to us at help@firecrawl.com
Thanks,
Firecrawl Team
",
- },
+ html: "Hey there,
Your auto recharge failed. Please try again manually. If the issue persists, please reach out to us at help@firecrawl.com
Thanks,
Firecrawl Team
"
+ }
};
export async function sendNotification(
@@ -55,7 +55,7 @@ export async function sendNotification(
export async function sendEmailNotification(
email: string,
- notificationType: NotificationType,
+ notificationType: NotificationType
) {
const resend = new Resend(process.env.RESEND_API_KEY);
@@ -65,7 +65,7 @@ export async function sendEmailNotification(
to: [email],
reply_to: "help@firecrawl.com",
subject: emailTemplates[notificationType].subject,
- html: emailTemplates[notificationType].html,
+ html: emailTemplates[notificationType].html
});
if (error) {
@@ -89,91 +89,97 @@ export async function sendNotificationInternal(
if (team_id === "preview") {
return { success: true };
}
- return await redlock.using([`notification-lock:${team_id}:${notificationType}`], 5000, async () => {
+ return await redlock.using(
+ [`notification-lock:${team_id}:${notificationType}`],
+ 5000,
+ async () => {
+ if (!bypassRecentChecks) {
+ const fifteenDaysAgo = new Date();
+ fifteenDaysAgo.setDate(fifteenDaysAgo.getDate() - 15);
- if (!bypassRecentChecks) {
- const fifteenDaysAgo = new Date();
- fifteenDaysAgo.setDate(fifteenDaysAgo.getDate() - 15);
+ const { data, error } = await supabase_service
+ .from("user_notifications")
+ .select("*")
+ .eq("team_id", team_id)
+ .eq("notification_type", notificationType)
+ .gte("sent_date", fifteenDaysAgo.toISOString());
- const { data, error } = await supabase_service
- .from("user_notifications")
- .select("*")
- .eq("team_id", team_id)
- .eq("notification_type", notificationType)
- .gte("sent_date", fifteenDaysAgo.toISOString());
+ if (error) {
+ logger.debug(`Error fetching notifications: ${error}`);
+ return { success: false };
+ }
- if (error) {
- logger.debug(`Error fetching notifications: ${error}`);
- return { success: false };
+ if (data.length !== 0) {
+ return { success: false };
+ }
+
+ // TODO: observation: Free credits people are not receiving notifications
+
+ const { data: recentData, error: recentError } = await supabase_service
+ .from("user_notifications")
+ .select("*")
+ .eq("team_id", team_id)
+ .eq("notification_type", notificationType)
+ .gte("sent_date", startDateString)
+ .lte("sent_date", endDateString);
+
+ if (recentError) {
+ logger.debug(
+ `Error fetching recent notifications: ${recentError.message}`
+ );
+ return { success: false };
+ }
+
+ if (recentData.length !== 0) {
+ return { success: false };
+ }
+ }
+
+ console.log(
+ `Sending notification for team_id: ${team_id} and notificationType: ${notificationType}`
+ );
+ // get the emails from the user with the team_id
+ const { data: emails, error: emailsError } = await supabase_service
+ .from("users")
+ .select("email")
+ .eq("team_id", team_id);
+
+ if (emailsError) {
+ logger.debug(`Error fetching emails: ${emailsError}`);
+ return { success: false };
+ }
+
+ for (const email of emails) {
+ await sendEmailNotification(email.email, notificationType);
+ }
+
+ const { error: insertError } = await supabase_service
+ .from("user_notifications")
+ .insert([
+ {
+ team_id: team_id,
+ notification_type: notificationType,
+ sent_date: new Date().toISOString(),
+ timestamp: new Date().toISOString()
+ }
+ ]);
+
+ if (process.env.SLACK_ADMIN_WEBHOOK_URL && emails.length > 0) {
+ sendSlackWebhook(
+ `${getNotificationString(notificationType)}: Team ${team_id}, with email ${emails[0].email}. Number of credits used: ${chunk.adjusted_credits_used} | Number of credits in the plan: ${chunk.price_credits}`,
+ false,
+ process.env.SLACK_ADMIN_WEBHOOK_URL
+ ).catch((error) => {
+ logger.debug(`Error sending slack notification: ${error}`);
+ });
+ }
+
+ if (insertError) {
+ logger.debug(`Error inserting notification record: ${insertError}`);
+ return { success: false };
+ }
+
+ return { success: true };
}
-
- if (data.length !== 0) {
- return { success: false };
- }
-
- // TODO: observation: Free credits people are not receiving notifications
-
- const { data: recentData, error: recentError } = await supabase_service
- .from("user_notifications")
- .select("*")
- .eq("team_id", team_id)
- .eq("notification_type", notificationType)
- .gte("sent_date", startDateString)
- .lte("sent_date", endDateString);
-
- if (recentError) {
- logger.debug(`Error fetching recent notifications: ${recentError.message}`);
- return { success: false };
- }
-
- if (recentData.length !== 0) {
- return { success: false };
- }
-
- }
-
- console.log(`Sending notification for team_id: ${team_id} and notificationType: ${notificationType}`);
- // get the emails from the user with the team_id
- const { data: emails, error: emailsError } = await supabase_service
- .from("users")
- .select("email")
- .eq("team_id", team_id);
-
- if (emailsError) {
- logger.debug(`Error fetching emails: ${emailsError}`);
- return { success: false };
- }
-
- for (const email of emails) {
- await sendEmailNotification(email.email, notificationType);
- }
-
- const { error: insertError } = await supabase_service
- .from("user_notifications")
- .insert([
- {
- team_id: team_id,
- notification_type: notificationType,
- sent_date: new Date().toISOString(),
- timestamp: new Date().toISOString(),
- },
- ]);
-
- if (process.env.SLACK_ADMIN_WEBHOOK_URL && emails.length > 0) {
- sendSlackWebhook(
- `${getNotificationString(notificationType)}: Team ${team_id}, with email ${emails[0].email}. Number of credits used: ${chunk.adjusted_credits_used} | Number of credits in the plan: ${chunk.price_credits}`,
- false,
- process.env.SLACK_ADMIN_WEBHOOK_URL
- ).catch((error) => {
- logger.debug(`Error sending slack notification: ${error}`);
- });
- }
-
- if (insertError) {
- logger.debug(`Error inserting notification record: ${insertError}`);
- return { success: false };
- }
-
- return { success: true };
- });
+ );
}
diff --git a/apps/api/src/services/posthog.ts b/apps/api/src/services/posthog.ts
index e3a01353..69f370ec 100644
--- a/apps/api/src/services/posthog.ts
+++ b/apps/api/src/services/posthog.ts
@@ -1,6 +1,6 @@
-import { PostHog } from 'posthog-node';
+import { PostHog } from "posthog-node";
import "dotenv/config";
-import { logger } from '../../src/lib/logger';
+import { logger } from "../../src/lib/logger";
export default function PostHogClient(apiKey: string) {
const posthogClient = new PostHog(apiKey, {
@@ -24,4 +24,4 @@ export const posthog = process.env.POSTHOG_API_KEY
"POSTHOG_API_KEY is not provided - your events will not be logged. Using MockPostHog as a fallback. See posthog.ts for more."
);
return new MockPostHog();
- })();
\ No newline at end of file
+ })();
diff --git a/apps/api/src/services/queue-jobs.ts b/apps/api/src/services/queue-jobs.ts
index bc2debfe..b4bd799b 100644
--- a/apps/api/src/services/queue-jobs.ts
+++ b/apps/api/src/services/queue-jobs.ts
@@ -3,7 +3,13 @@ import { getScrapeQueue } from "./queue-service";
import { v4 as uuidv4 } from "uuid";
import { WebScraperOptions } from "../types";
import * as Sentry from "@sentry/node";
-import { cleanOldConcurrencyLimitEntries, getConcurrencyLimitActiveJobs, getConcurrencyLimitMax, pushConcurrencyLimitActiveJob, pushConcurrencyLimitedJob } from "../lib/concurrency-limit";
+import {
+ cleanOldConcurrencyLimitEntries,
+ getConcurrencyLimitActiveJobs,
+ getConcurrencyLimitMax,
+ pushConcurrencyLimitActiveJob,
+ pushConcurrencyLimitedJob
+} from "../lib/concurrency-limit";
async function addScrapeJobRaw(
webScraperOptions: any,
@@ -13,11 +19,17 @@ async function addScrapeJobRaw(
) {
let concurrencyLimited = false;
- if (webScraperOptions && webScraperOptions.team_id && webScraperOptions.plan) {
+ if (
+ webScraperOptions &&
+ webScraperOptions.team_id &&
+ webScraperOptions.plan
+ ) {
const now = Date.now();
const limit = await getConcurrencyLimitMax(webScraperOptions.plan);
cleanOldConcurrencyLimitEntries(webScraperOptions.team_id, now);
- concurrencyLimited = (await getConcurrencyLimitActiveJobs(webScraperOptions.team_id, now)).length >= limit;
+ concurrencyLimited =
+ (await getConcurrencyLimitActiveJobs(webScraperOptions.team_id, now))
+ .length >= limit;
}
if (concurrencyLimited) {
@@ -27,19 +39,23 @@ async function addScrapeJobRaw(
opts: {
...options,
priority: jobPriority,
- jobId: jobId,
+ jobId: jobId
},
- priority: jobPriority,
+ priority: jobPriority
});
} else {
- if (webScraperOptions && webScraperOptions.team_id && webScraperOptions.plan) {
+ if (
+ webScraperOptions &&
+ webScraperOptions.team_id &&
+ webScraperOptions.plan
+ ) {
await pushConcurrencyLimitActiveJob(webScraperOptions.team_id, jobId);
}
await getScrapeQueue().add(jobId, webScraperOptions, {
...options,
priority: jobPriority,
- jobId,
+ jobId
});
}
}
@@ -52,24 +68,32 @@ export async function addScrapeJob(
) {
if (Sentry.isInitialized()) {
const size = JSON.stringify(webScraperOptions).length;
- return await Sentry.startSpan({
- name: "Add scrape job",
- op: "queue.publish",
- attributes: {
- "messaging.message.id": jobId,
- "messaging.destination.name": getScrapeQueue().name,
- "messaging.message.body.size": size,
+ return await Sentry.startSpan(
+ {
+ name: "Add scrape job",
+ op: "queue.publish",
+ attributes: {
+ "messaging.message.id": jobId,
+ "messaging.destination.name": getScrapeQueue().name,
+ "messaging.message.body.size": size
+ }
},
- }, async (span) => {
- await addScrapeJobRaw({
- ...webScraperOptions,
- sentry: {
- trace: Sentry.spanToTraceHeader(span),
- baggage: Sentry.spanToBaggageHeader(span),
- size,
- },
- }, options, jobId, jobPriority);
- });
+ async (span) => {
+ await addScrapeJobRaw(
+ {
+ ...webScraperOptions,
+ sentry: {
+ trace: Sentry.spanToTraceHeader(span),
+ baggage: Sentry.spanToBaggageHeader(span),
+ size
+ }
+ },
+ options,
+ jobId,
+ jobPriority
+ );
+ }
+ );
} else {
await addScrapeJobRaw(webScraperOptions, options, jobId, jobPriority);
}
@@ -77,18 +101,25 @@ export async function addScrapeJob(
export async function addScrapeJobs(
jobs: {
- data: WebScraperOptions,
+ data: WebScraperOptions;
opts: {
- jobId: string,
- priority: number,
- },
- }[],
+ jobId: string;
+ priority: number;
+ };
+ }[]
) {
// TODO: better
- await Promise.all(jobs.map(job => addScrapeJob(job.data, job.opts, job.opts.jobId, job.opts.priority)));
+ await Promise.all(
+ jobs.map((job) =>
+ addScrapeJob(job.data, job.opts, job.opts.jobId, job.opts.priority)
+ )
+ );
}
-export function waitForJob(jobId: string, timeout: number): Promise {
+export function waitForJob(
+ jobId: string,
+ timeout: number
+): Promise {
return new Promise((resolve, reject) => {
const start = Date.now();
const int = setInterval(async () => {
@@ -110,5 +141,5 @@ export function waitForJob(jobId: string, timeout: number): Promise
}
}
}, 250);
- })
+ });
}
diff --git a/apps/api/src/services/queue-service.ts b/apps/api/src/services/queue-service.ts
index e6432a3f..3970a6e7 100644
--- a/apps/api/src/services/queue-service.ts
+++ b/apps/api/src/services/queue-service.ts
@@ -5,7 +5,7 @@ import IORedis from "ioredis";
let scrapeQueue: Queue;
export const redisConnection = new IORedis(process.env.REDIS_URL!, {
- maxRetriesPerRequest: null,
+ maxRetriesPerRequest: null
});
export const scrapeQueueName = "{scrapeQueue}";
@@ -18,12 +18,12 @@ export function getScrapeQueue() {
connection: redisConnection,
defaultJobOptions: {
removeOnComplete: {
- age: 90000, // 25 hours
+ age: 90000 // 25 hours
},
removeOnFail: {
- age: 90000, // 25 hours
- },
- },
+ age: 90000 // 25 hours
+ }
+ }
}
// {
// settings: {
@@ -42,7 +42,6 @@ export function getScrapeQueue() {
return scrapeQueue;
}
-
// === REMOVED IN FAVOR OF POLLING -- NOT RELIABLE
// import { QueueEvents } from 'bullmq';
-// export const scrapeQueueEvents = new QueueEvents(scrapeQueueName, { connection: redisConnection.duplicate() });
\ No newline at end of file
+// export const scrapeQueueEvents = new QueueEvents(scrapeQueueName, { connection: redisConnection.duplicate() });
diff --git a/apps/api/src/services/queue-worker.ts b/apps/api/src/services/queue-worker.ts
index 74e954cd..dc352d36 100644
--- a/apps/api/src/services/queue-worker.ts
+++ b/apps/api/src/services/queue-worker.ts
@@ -5,7 +5,7 @@ import { CustomError } from "../lib/custom-error";
import {
getScrapeQueue,
redisConnection,
- scrapeQueueName,
+ scrapeQueueName
} from "./queue-service";
import { startWebScraperPipeline } from "../main/runWebScraper";
import { callWebhook } from "./webhook";
@@ -24,26 +24,31 @@ import {
getCrawl,
getCrawlJobs,
lockURL,
- normalizeURL,
+ normalizeURL
} from "../lib/crawl-redis";
import { StoredCrawl } from "../lib/crawl-redis";
import { addScrapeJob } from "./queue-jobs";
import {
addJobPriority,
deleteJobPriority,
- getJobPriority,
+ getJobPriority
} from "../../src/lib/job-priority";
import { PlanType, RateLimiterMode } from "../types";
import { getJobs } from "..//controllers/v1/crawl-status";
import { configDotenv } from "dotenv";
import { scrapeOptions } from "../controllers/v1/types";
import { getRateLimiterPoints } from "./rate-limiter";
-import { cleanOldConcurrencyLimitEntries, pushConcurrencyLimitActiveJob, removeConcurrencyLimitActiveJob, takeConcurrencyLimitedJob } from "../lib/concurrency-limit";
+import {
+ cleanOldConcurrencyLimitEntries,
+ pushConcurrencyLimitActiveJob,
+ removeConcurrencyLimitActiveJob,
+ takeConcurrencyLimitedJob
+} from "../lib/concurrency-limit";
configDotenv();
class RacedRedirectError extends Error {
constructor() {
- super("Raced redirect error")
+ super("Raced redirect error");
}
}
@@ -63,21 +68,28 @@ const connectionMonitorInterval =
Number(process.env.CONNECTION_MONITOR_INTERVAL) || 10;
const gotJobInterval = Number(process.env.CONNECTION_MONITOR_INTERVAL) || 20;
-async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
+async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
if (await finishCrawl(job.data.crawl_id)) {
if (!job.data.v1) {
const jobIDs = await getCrawlJobs(job.data.crawl_id);
- const jobs = (await getJobs(jobIDs)).sort((a, b) => a.timestamp - b.timestamp);
+ const jobs = (await getJobs(jobIDs)).sort(
+ (a, b) => a.timestamp - b.timestamp
+ );
// const jobStatuses = await Promise.all(jobs.map((x) => x.getState()));
- const jobStatus =
- sc.cancelled // || jobStatuses.some((x) => x === "failed")
- ? "failed"
- : "completed";
+ const jobStatus = sc.cancelled // || jobStatuses.some((x) => x === "failed")
+ ? "failed"
+ : "completed";
- const fullDocs = jobs.map((x) =>
- x.returnvalue ? (Array.isArray(x.returnvalue) ? x.returnvalue[0] : x.returnvalue) : null
- ).filter(x => x !== null);
+ const fullDocs = jobs
+ .map((x) =>
+ x.returnvalue
+ ? Array.isArray(x.returnvalue)
+ ? x.returnvalue[0]
+ : x.returnvalue
+ : null
+ )
+ .filter((x) => x !== null);
await logJob({
job_id: job.data.crawl_id,
@@ -91,7 +103,7 @@ async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
url: sc.originUrl!,
scrapeOptions: sc.scrapeOptions,
crawlerOptions: sc.crawlerOptions,
- origin: job.data.origin,
+ origin: job.data.origin
});
const data = {
@@ -100,12 +112,12 @@ async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
links: fullDocs.map((doc) => {
return {
content: doc,
- source: doc?.metadata?.sourceURL ?? doc?.url ?? "",
+ source: doc?.metadata?.sourceURL ?? doc?.url ?? ""
};
- }),
+ })
},
project_id: job.data.project_id,
- docs: fullDocs,
+ docs: fullDocs
};
// v0 web hooks, call when done with all the data
@@ -116,15 +128,14 @@ async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
data,
job.data.webhook,
job.data.v1,
- job.data.crawlerOptions !== null ? "crawl.completed" : "batch_scrape.completed"
+ job.data.crawlerOptions !== null
+ ? "crawl.completed"
+ : "batch_scrape.completed"
);
}
} else {
const jobIDs = await getCrawlJobs(job.data.crawl_id);
- const jobStatus =
- sc.cancelled
- ? "failed"
- : "completed";
+ const jobStatus = sc.cancelled ? "failed" : "completed";
// v1 web hooks, call when done with no data, but with event completed
if (job.data.v1 && job.data.webhook) {
@@ -134,30 +145,43 @@ async function finishCrawlIfNeeded(job: Job & { id: string }, sc: StoredCrawl) {
[],
job.data.webhook,
job.data.v1,
- job.data.crawlerOptions !== null ? "crawl.completed" : "batch_scrape.completed"
- );
- }
+ job.data.crawlerOptions !== null
+ ? "crawl.completed"
+ : "batch_scrape.completed"
+ );
+ }
- await logJob({
- job_id: job.data.crawl_id,
- success: jobStatus === "completed",
- message: sc.cancelled ? "Cancelled" : undefined,
- num_docs: jobIDs.length,
- docs: [],
- time_taken: (Date.now() - sc.createdAt) / 1000,
- team_id: job.data.team_id,
- scrapeOptions: sc.scrapeOptions,
- mode: job.data.crawlerOptions !== null ? "crawl" : "batch_scrape",
- url: sc?.originUrl ?? (job.data.crawlerOptions === null ? "Batch Scrape" : "Unknown"),
- crawlerOptions: sc.crawlerOptions,
- origin: job.data.origin,
- }, true);
+ await logJob(
+ {
+ job_id: job.data.crawl_id,
+ success: jobStatus === "completed",
+ message: sc.cancelled ? "Cancelled" : undefined,
+ num_docs: jobIDs.length,
+ docs: [],
+ time_taken: (Date.now() - sc.createdAt) / 1000,
+ team_id: job.data.team_id,
+ scrapeOptions: sc.scrapeOptions,
+ mode: job.data.crawlerOptions !== null ? "crawl" : "batch_scrape",
+ url:
+ sc?.originUrl ??
+ (job.data.crawlerOptions === null ? "Batch Scrape" : "Unknown"),
+ crawlerOptions: sc.crawlerOptions,
+ origin: job.data.origin
+ },
+ true
+ );
}
}
}
const processJobInternal = async (token: string, job: Job & { id: string }) => {
- const logger = _logger.child({ module: "queue-worker", method: "processJobInternal", jobId: job.id, scrapeId: job.id, crawlId: job.data?.crawl_id ?? undefined });
+ const logger = _logger.child({
+ module: "queue-worker",
+ method: "processJobInternal",
+ jobId: job.id,
+ scrapeId: job.id,
+ crawlId: job.data?.crawl_id ?? undefined
+ });
const extendLockInterval = setInterval(async () => {
logger.info(`🐂 Worker extending lock on job ${job.id}`);
@@ -171,7 +195,9 @@ const processJobInternal = async (token: string, job: Job & { id: string }) => {
if (result.success) {
try {
if (job.data.crawl_id && process.env.USE_DB_AUTHENTICATION === "true") {
- logger.debug("Job succeeded -- has crawl associated, putting null in Redis");
+ logger.debug(
+ "Job succeeded -- has crawl associated, putting null in Redis"
+ );
await job.moveToCompleted(null, token, false);
} else {
logger.debug("Job succeeded -- putting result in Redis");
@@ -220,7 +246,7 @@ const workerFun = async (
lockDuration: 1 * 60 * 1000, // 1 minute
// lockRenewTime: 15 * 1000, // 15 seconds
stalledInterval: 30 * 1000, // 30 seconds
- maxStalledCount: 10, // 10 times
+ maxStalledCount: 10 // 10 times
});
worker.startStalledCheckTimer();
@@ -241,7 +267,7 @@ const workerFun = async (
if (cantAcceptConnectionCount >= 25) {
logger.error("WORKER STALLED", {
cpuUsage: await monitor.checkCpuUsage(),
- memoryUsage: await monitor.checkMemoryUsage(),
+ memoryUsage: await monitor.checkMemoryUsage()
});
}
@@ -265,14 +291,18 @@ const workerFun = async (
if (nextJob !== null) {
await pushConcurrencyLimitActiveJob(job.data.team_id, nextJob.id);
- await queue.add(nextJob.id, {
- ...nextJob.data,
- concurrencyLimitHit: true,
- }, {
- ...nextJob.opts,
- jobId: nextJob.id,
- priority: nextJob.priority,
- });
+ await queue.add(
+ nextJob.id,
+ {
+ ...nextJob.data,
+ concurrencyLimitHit: true
+ },
+ {
+ ...nextJob.opts,
+ jobId: nextJob.id,
+ priority: nextJob.priority
+ }
+ );
}
}
}
@@ -281,7 +311,7 @@ const workerFun = async (
Sentry.continueTrace(
{
sentryTrace: job.data.sentry.trace,
- baggage: job.data.sentry.baggage,
+ baggage: job.data.sentry.baggage
},
() => {
Sentry.startSpan(
@@ -289,8 +319,8 @@ const workerFun = async (
name: "Scrape job",
attributes: {
job: job.id,
- worker: process.env.FLY_MACHINE_ID ?? worker.id,
- },
+ worker: process.env.FLY_MACHINE_ID ?? worker.id
+ }
},
async (span) => {
await Sentry.startSpan(
@@ -303,17 +333,17 @@ const workerFun = async (
"messaging.message.body.size": job.data.sentry.size,
"messaging.message.receive.latency":
Date.now() - (job.processedOn ?? job.timestamp),
- "messaging.message.retry.count": job.attemptsMade,
- },
+ "messaging.message.retry.count": job.attemptsMade
+ }
},
async () => {
let res;
try {
res = await processJobInternal(token, job);
- } finally {
- await afterJobDone(job)
+ } finally {
+ await afterJobDone(job);
}
-
+
if (res !== null) {
span.setStatus({ code: 2 }); // ERROR
} else {
@@ -331,12 +361,11 @@ const workerFun = async (
name: "Scrape job",
attributes: {
job: job.id,
- worker: process.env.FLY_MACHINE_ID ?? worker.id,
- },
+ worker: process.env.FLY_MACHINE_ID ?? worker.id
+ }
},
() => {
- processJobInternal(token, job)
- .finally(() => afterJobDone(job));
+ processJobInternal(token, job).finally(() => afterJobDone(job));
}
);
}
@@ -351,7 +380,13 @@ const workerFun = async (
workerFun(getScrapeQueue(), processJobInternal);
async function processJob(job: Job & { id: string }, token: string) {
- const logger = _logger.child({ module: "queue-worker", method: "processJob", jobId: job.id, scrapeId: job.id, crawlId: job.data?.crawl_id ?? undefined });
+ const logger = _logger.child({
+ module: "queue-worker",
+ method: "processJob",
+ jobId: job.id,
+ scrapeId: job.id,
+ crawlId: job.data?.crawl_id ?? undefined
+ });
logger.info(`🐂 Worker taking job ${job.id}`, { url: job.data.url });
// Check if the job URL is researchhub and block it immediately
@@ -368,7 +403,7 @@ async function processJob(job: Job & { id: string }, token: string) {
document: null,
project_id: job.data.project_id,
error:
- "URL is blocked. Suspecious activity detected. Please contact help@firecrawl.com if you believe this is an error.",
+ "URL is blocked. Suspecious activity detected. Please contact help@firecrawl.com if you believe this is an error."
};
return data;
}
@@ -378,21 +413,23 @@ async function processJob(job: Job & { id: string }, token: string) {
current: 1,
total: 100,
current_step: "SCRAPING",
- current_url: "",
+ current_url: ""
});
const start = Date.now();
const pipeline = await Promise.race([
startWebScraperPipeline({
job,
- token,
+ token
}),
- ...(job.data.scrapeOptions.timeout !== undefined ? [
- (async () => {
- await sleep(job.data.scrapeOptions.timeout);
- throw new Error("timeout")
- })(),
- ] : [])
+ ...(job.data.scrapeOptions.timeout !== undefined
+ ? [
+ (async () => {
+ await sleep(job.data.scrapeOptions.timeout);
+ throw new Error("timeout");
+ })()
+ ]
+ : [])
]);
if (!pipeline.success) {
@@ -410,17 +447,21 @@ async function processJob(job: Job & { id: string }, token: string) {
const data = {
success: true,
result: {
- links: [{
- content: doc,
- source: doc?.metadata?.sourceURL ?? doc?.metadata?.url ?? "",
- }],
+ links: [
+ {
+ content: doc,
+ source: doc?.metadata?.sourceURL ?? doc?.metadata?.url ?? ""
+ }
+ ]
},
project_id: job.data.project_id,
- document: doc,
+ document: doc
};
if (job.data.webhook && job.data.mode !== "crawl" && job.data.v1) {
- logger.debug("Calling webhook with success...", { webhook: job.data.webhook });
+ logger.debug("Calling webhook with success...", {
+ webhook: job.data.webhook
+ });
await callWebhook(
job.data.team_id,
job.data.crawl_id,
@@ -434,54 +475,83 @@ async function processJob(job: Job & { id: string }, token: string) {
if (job.data.crawl_id) {
const sc = (await getCrawl(job.data.crawl_id)) as StoredCrawl;
-
- if (doc.metadata.url !== undefined && doc.metadata.sourceURL !== undefined && normalizeURL(doc.metadata.url, sc) !== normalizeURL(doc.metadata.sourceURL, sc)) {
- logger.debug("Was redirected, removing old URL and locking new URL...", { oldUrl: doc.metadata.sourceURL, newUrl: doc.metadata.url });
+
+ if (
+ doc.metadata.url !== undefined &&
+ doc.metadata.sourceURL !== undefined &&
+ normalizeURL(doc.metadata.url, sc) !==
+ normalizeURL(doc.metadata.sourceURL, sc)
+ ) {
+ logger.debug(
+ "Was redirected, removing old URL and locking new URL...",
+ { oldUrl: doc.metadata.sourceURL, newUrl: doc.metadata.url }
+ );
// Remove the old URL from visited unique due to checking for limit
// Do not remove from :visited otherwise it will keep crawling the original URL (sourceURL)
- await redisConnection.srem("crawl:" + job.data.crawl_id + ":visited_unique", normalizeURL(doc.metadata.sourceURL, sc));
+ await redisConnection.srem(
+ "crawl:" + job.data.crawl_id + ":visited_unique",
+ normalizeURL(doc.metadata.sourceURL, sc)
+ );
const p1 = generateURLPermutations(normalizeURL(doc.metadata.url, sc));
- const p2 = generateURLPermutations(normalizeURL(doc.metadata.sourceURL, sc));
+ const p2 = generateURLPermutations(
+ normalizeURL(doc.metadata.sourceURL, sc)
+ );
// In crawls, we should only crawl a redirected page once, no matter how many; times it is redirected to, or if it's been discovered by the crawler before.
// This can prevent flakiness with race conditions.
// Lock the new URL
const lockRes = await lockURL(job.data.crawl_id, sc, doc.metadata.url);
- if (job.data.crawlerOptions !== null && !lockRes && JSON.stringify(p1) !== JSON.stringify(p2)) {
+ if (
+ job.data.crawlerOptions !== null &&
+ !lockRes &&
+ JSON.stringify(p1) !== JSON.stringify(p2)
+ ) {
throw new RacedRedirectError();
}
}
logger.debug("Logging job to DB...");
- await logJob({
- job_id: job.id as string,
- success: true,
- num_docs: 1,
- docs: [doc],
- time_taken: timeTakenInSeconds,
- team_id: job.data.team_id,
- mode: job.data.mode,
- url: job.data.url,
- crawlerOptions: sc.crawlerOptions,
- scrapeOptions: job.data.scrapeOptions,
- origin: job.data.origin,
- crawl_id: job.data.crawl_id,
- }, true);
+ await logJob(
+ {
+ job_id: job.id as string,
+ success: true,
+ num_docs: 1,
+ docs: [doc],
+ time_taken: timeTakenInSeconds,
+ team_id: job.data.team_id,
+ mode: job.data.mode,
+ url: job.data.url,
+ crawlerOptions: sc.crawlerOptions,
+ scrapeOptions: job.data.scrapeOptions,
+ origin: job.data.origin,
+ crawl_id: job.data.crawl_id
+ },
+ true
+ );
logger.debug("Declaring job as done...");
await addCrawlJobDone(job.data.crawl_id, job.id, true);
if (job.data.crawlerOptions !== null) {
if (!sc.cancelled) {
- const crawler = crawlToCrawler(job.data.crawl_id, sc, doc.metadata.url ?? doc.metadata.sourceURL ?? sc.originUrl!);
+ const crawler = crawlToCrawler(
+ job.data.crawl_id,
+ sc,
+ doc.metadata.url ?? doc.metadata.sourceURL ?? sc.originUrl!
+ );
const links = crawler.filterLinks(
- crawler.extractLinksFromHTML(rawHtml ?? "", doc.metadata?.url ?? doc.metadata?.sourceURL ?? sc.originUrl!),
+ crawler.extractLinksFromHTML(
+ rawHtml ?? "",
+ doc.metadata?.url ?? doc.metadata?.sourceURL ?? sc.originUrl!
+ ),
Infinity,
sc.crawlerOptions?.maxDepth ?? 10
);
- logger.debug("Discovered " + links.length + " links...", { linksLength: links.length });
+ logger.debug("Discovered " + links.length + " links...", {
+ linksLength: links.length
+ });
for (const link of links) {
if (await lockURL(job.data.crawl_id, sc, link)) {
@@ -489,11 +559,17 @@ async function processJob(job: Job & { id: string }, token: string) {
const jobPriority = await getJobPriority({
plan: sc.plan as PlanType,
team_id: sc.team_id,
- basePriority: job.data.crawl_id ? 20 : 10,
+ basePriority: job.data.crawl_id ? 20 : 10
});
const jobId = uuidv4();
- logger.debug("Determined job priority " + jobPriority + " for URL " + JSON.stringify(link), { jobPriority, url: link });
+ logger.debug(
+ "Determined job priority " +
+ jobPriority +
+ " for URL " +
+ JSON.stringify(link),
+ { jobPriority, url: link }
+ );
// console.log("plan: ", sc.plan);
// console.log("team_id: ", sc.team_id)
@@ -511,7 +587,7 @@ async function processJob(job: Job & { id: string }, token: string) {
origin: job.data.origin,
crawl_id: job.data.crawl_id,
webhook: job.data.webhook,
- v1: job.data.v1,
+ v1: job.data.v1
},
{},
jobId,
@@ -519,9 +595,15 @@ async function processJob(job: Job & { id: string }, token: string) {
);
await addCrawlJob(job.data.crawl_id, jobId);
- logger.debug("Added job for URL " + JSON.stringify(link), { jobPriority, url: link, newJobId: jobId });
+ logger.debug("Added job for URL " + JSON.stringify(link), {
+ jobPriority,
+ url: link,
+ newJobId: jobId
+ });
} else {
- logger.debug("Could not lock URL " + JSON.stringify(link), { url: link });
+ logger.debug("Could not lock URL " + JSON.stringify(link), {
+ url: link
+ });
}
}
}
@@ -533,7 +615,8 @@ async function processJob(job: Job & { id: string }, token: string) {
logger.info(`🐂 Job done ${job.id}`);
return data;
} catch (error) {
- const isEarlyTimeout = error instanceof Error && error.message === "timeout";
+ const isEarlyTimeout =
+ error instanceof Error && error.message === "timeout";
if (isEarlyTimeout) {
logger.error(`🐂 Job timed out ${job.id}`);
@@ -544,8 +627,8 @@ async function processJob(job: Job & { id: string }, token: string) {
Sentry.captureException(error, {
data: {
- job: job.id,
- },
+ job: job.id
+ }
});
if (error instanceof CustomError) {
@@ -562,7 +645,12 @@ async function processJob(job: Job & { id: string }, token: string) {
success: false,
document: null,
project_id: job.data.project_id,
- error: error instanceof Error ? error : typeof error === "string" ? new Error(error) : new Error(JSON.stringify(error)),
+ error:
+ error instanceof Error
+ ? error
+ : typeof error === "string"
+ ? new Error(error)
+ : new Error(JSON.stringify(error))
};
if (!job.data.v1 && (job.data.mode === "crawl" || job.data.crawl_id)) {
@@ -572,7 +660,7 @@ async function processJob(job: Job & { id: string }, token: string) {
data,
job.data.webhook,
job.data.v1,
- job.data.crawlerOptions !== null ? "crawl.page" : "batch_scrape.page",
+ job.data.crawlerOptions !== null ? "crawl.page" : "batch_scrape.page"
);
}
// if (job.data.v1) {
@@ -588,31 +676,34 @@ async function processJob(job: Job & { id: string }, token: string) {
if (job.data.crawl_id) {
const sc = (await getCrawl(job.data.crawl_id)) as StoredCrawl;
-
+
logger.debug("Declaring job as done...");
await addCrawlJobDone(job.data.crawl_id, job.id, false);
logger.debug("Logging job to DB...");
- await logJob({
- job_id: job.id as string,
- success: false,
- message:
- typeof error === "string"
- ? error
- : error.message ??
- "Something went wrong... Contact help@mendable.ai",
- num_docs: 0,
- docs: [],
- time_taken: 0,
- team_id: job.data.team_id,
- mode: job.data.mode,
- url: job.data.url,
- crawlerOptions: sc.crawlerOptions,
- scrapeOptions: job.data.scrapeOptions,
- origin: job.data.origin,
- crawl_id: job.data.crawl_id,
- }, true);
-
+ await logJob(
+ {
+ job_id: job.id as string,
+ success: false,
+ message:
+ typeof error === "string"
+ ? error
+ : (error.message ??
+ "Something went wrong... Contact help@mendable.ai"),
+ num_docs: 0,
+ docs: [],
+ time_taken: 0,
+ team_id: job.data.team_id,
+ mode: job.data.mode,
+ url: job.data.url,
+ crawlerOptions: sc.crawlerOptions,
+ scrapeOptions: job.data.scrapeOptions,
+ origin: job.data.origin,
+ crawl_id: job.data.crawl_id
+ },
+ true
+ );
+
await finishCrawlIfNeeded(job, sc);
// await logJob({
diff --git a/apps/api/src/services/rate-limiter.test.ts b/apps/api/src/services/rate-limiter.test.ts
index 4052bfff..5c25a8d7 100644
--- a/apps/api/src/services/rate-limiter.test.ts
+++ b/apps/api/src/services/rate-limiter.test.ts
@@ -2,7 +2,7 @@ import {
getRateLimiter,
serverRateLimiter,
testSuiteRateLimiter,
- redisRateLimitClient,
+ redisRateLimitClient
} from "./rate-limiter";
import { RateLimiterMode } from "../../src/types";
import { RateLimiterRedis } from "rate-limiter-flexible";
@@ -25,7 +25,7 @@ describe("Rate Limiter Service", () => {
afterAll(async () => {
try {
// if (process.env.REDIS_RATE_LIMIT_URL === "redis://localhost:6379") {
- await redisRateLimitClient.disconnect();
+ await redisRateLimitClient.disconnect();
// }
} catch (error) {}
});
@@ -103,7 +103,7 @@ describe("Rate Limiter Service", () => {
storeClient: redisRateLimitClient,
keyPrefix,
points,
- duration: 60,
+ duration: 60
});
expect(limiter.keyPrefix).toBe(keyPrefix);
@@ -357,7 +357,7 @@ describe("Rate Limiter Service", () => {
storeClient: redisRateLimitClient,
keyPrefix,
points,
- duration,
+ duration
});
const consumePoints = 5;
diff --git a/apps/api/src/services/rate-limiter.ts b/apps/api/src/services/rate-limiter.ts
index 5eecfa70..8067f862 100644
--- a/apps/api/src/services/rate-limiter.ts
+++ b/apps/api/src/services/rate-limiter.ts
@@ -18,7 +18,7 @@ const RATE_LIMITS = {
etier2c: 300,
etier1a: 1000,
etier2a: 300,
- etierscale1: 150,
+ etierscale1: 150
},
scrape: {
default: 20,
@@ -35,7 +35,7 @@ const RATE_LIMITS = {
etier2c: 2500,
etier1a: 1000,
etier2a: 2500,
- etierscale1: 1500,
+ etierscale1: 1500
},
search: {
default: 20,
@@ -52,9 +52,9 @@ const RATE_LIMITS = {
etier2c: 2500,
etier1a: 1000,
etier2a: 2500,
- etierscale1: 1500,
+ etierscale1: 1500
},
- map:{
+ map: {
default: 20,
free: 5,
starter: 50,
@@ -69,36 +69,36 @@ const RATE_LIMITS = {
etier2c: 2500,
etier1a: 1000,
etier2a: 2500,
- etierscale1: 1500,
+ etierscale1: 1500
},
preview: {
free: 5,
- default: 5,
+ default: 5
},
account: {
free: 100,
- default: 100,
+ default: 100
},
crawlStatus: {
free: 300,
- default: 500,
+ default: 500
},
testSuite: {
free: 10000,
- default: 10000,
- },
+ default: 10000
+ }
};
export const redisRateLimitClient = new Redis(
process.env.REDIS_RATE_LIMIT_URL!
-)
+);
const createRateLimiter = (keyPrefix, points) =>
new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix,
points,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
export const serverRateLimiter = createRateLimiter(
@@ -110,43 +110,42 @@ export const testSuiteRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "test-suite",
points: 10000,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
export const devBRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "dev-b",
points: 1200,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
export const manualRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "manual",
points: 2000,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
-
export const scrapeStatusRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "scrape-status",
points: 400,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
export const etier1aRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "etier1a",
points: 10000,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
export const etier2aRateLimiter = new RateLimiterRedis({
storeClient: redisRateLimitClient,
keyPrefix: "etier2a",
points: 2500,
- duration: 60, // Duration in seconds
+ duration: 60 // Duration in seconds
});
const testSuiteTokens = [
@@ -180,12 +179,12 @@ export function getRateLimiterPoints(
token?: string,
plan?: string,
teamId?: string
-) : number {
+): number {
const rateLimitConfig = RATE_LIMITS[mode]; // {default : 5}
if (!rateLimitConfig) return RATE_LIMITS.account.default;
-
- const points : number =
+
+ const points: number =
rateLimitConfig[makePlanKey(plan)] || rateLimitConfig.default; // 5
return points;
}
@@ -195,30 +194,33 @@ export function getRateLimiter(
token?: string,
plan?: string,
teamId?: string
- ) : RateLimiterRedis {
- if (token && testSuiteTokens.some(testToken => token.includes(testToken))) {
+): RateLimiterRedis {
+ if (token && testSuiteTokens.some((testToken) => token.includes(testToken))) {
return testSuiteRateLimiter;
}
- if(teamId && teamId === process.env.DEV_B_TEAM_ID) {
+ if (teamId && teamId === process.env.DEV_B_TEAM_ID) {
return devBRateLimiter;
}
-
- if(teamId && teamId === process.env.ETIER1A_TEAM_ID) {
+
+ if (teamId && teamId === process.env.ETIER1A_TEAM_ID) {
return etier1aRateLimiter;
}
- if(teamId && teamId === process.env.ETIER2A_TEAM_ID) {
+ if (teamId && teamId === process.env.ETIER2A_TEAM_ID) {
return etier2aRateLimiter;
}
- if(teamId && teamId === process.env.ETIER2D_TEAM_ID) {
+ if (teamId && teamId === process.env.ETIER2D_TEAM_ID) {
return etier2aRateLimiter;
}
- if(teamId && manual.includes(teamId)) {
+ if (teamId && manual.includes(teamId)) {
return manualRateLimiter;
}
-
- return createRateLimiter(`${mode}-${makePlanKey(plan)}`, getRateLimiterPoints(mode, token, plan, teamId));
+
+ return createRateLimiter(
+ `${mode}-${makePlanKey(plan)}`,
+ getRateLimiterPoints(mode, token, plan, teamId)
+ );
}
diff --git a/apps/api/src/services/redis.ts b/apps/api/src/services/redis.ts
index 1bd83605..04fcbd5e 100644
--- a/apps/api/src/services/redis.ts
+++ b/apps/api/src/services/redis.ts
@@ -35,7 +35,12 @@ redisRateLimitClient.on("connect", (err) => {
* @param {string} value The value to store.
* @param {number} [expire] Optional expiration time in seconds.
*/
-const setValue = async (key: string, value: string, expire?: number, nx = false) => {
+const setValue = async (
+ key: string,
+ value: string,
+ expire?: number,
+ nx = false
+) => {
if (expire && !nx) {
await redisRateLimitClient.set(key, value, "EX", expire);
} else {
diff --git a/apps/api/src/services/redlock.ts b/apps/api/src/services/redlock.ts
index 921a973a..757346f9 100644
--- a/apps/api/src/services/redlock.ts
+++ b/apps/api/src/services/redlock.ts
@@ -21,6 +21,6 @@ export const redlock = new Redlock(
// The minimum remaining time on a lock before an extension is automatically
// attempted with the `using` API.
- automaticExtensionThreshold: 500, // time in ms
+ automaticExtensionThreshold: 500 // time in ms
}
);
diff --git a/apps/api/src/services/sentry.ts b/apps/api/src/services/sentry.ts
index 072a501e..41f19362 100644
--- a/apps/api/src/services/sentry.ts
+++ b/apps/api/src/services/sentry.ts
@@ -7,12 +7,10 @@ if (process.env.SENTRY_DSN) {
logger.info("Setting up Sentry...");
Sentry.init({
dsn: process.env.SENTRY_DSN,
- integrations: [
- nodeProfilingIntegration(),
- ],
+ integrations: [nodeProfilingIntegration()],
tracesSampleRate: process.env.SENTRY_ENVIRONMENT === "dev" ? 1.0 : 0.045,
profilesSampleRate: 1.0,
serverName: process.env.FLY_MACHINE_ID,
- environment: process.env.SENTRY_ENVIRONMENT ?? "production",
+ environment: process.env.SENTRY_ENVIRONMENT ?? "production"
});
}
diff --git a/apps/api/src/services/supabase.ts b/apps/api/src/services/supabase.ts
index 61f16836..521a82ca 100644
--- a/apps/api/src/services/supabase.ts
+++ b/apps/api/src/services/supabase.ts
@@ -10,7 +10,7 @@ class SupabaseService {
constructor() {
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseServiceToken = process.env.SUPABASE_SERVICE_TOKEN;
- const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === 'true';
+ const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === "true";
// Only initialize the Supabase client if both URL and Service Token are provided.
if (!useDbAuthentication) {
// Warn the user that Authentication is disabled by setting the client to null
@@ -52,6 +52,6 @@ export const supabase_service: SupabaseClient = new Proxy(
}
// Otherwise, delegate access to the Supabase client.
return Reflect.get(client, prop, receiver);
- },
+ }
}
) as unknown as SupabaseClient;
diff --git a/apps/api/src/services/system-monitor.ts b/apps/api/src/services/system-monitor.ts
index b5e1bf29..4fa4c478 100644
--- a/apps/api/src/services/system-monitor.ts
+++ b/apps/api/src/services/system-monitor.ts
@@ -1,223 +1,228 @@
-import si from 'systeminformation';
+import si from "systeminformation";
import { Mutex } from "async-mutex";
-import os from 'os';
-import fs from 'fs';
-import { logger } from '../lib/logger';
+import os from "os";
+import fs from "fs";
+import { logger } from "../lib/logger";
const IS_KUBERNETES = process.env.IS_KUBERNETES === "true";
const MAX_CPU = process.env.MAX_CPU ? parseFloat(process.env.MAX_CPU) : 0.8;
const MAX_RAM = process.env.MAX_RAM ? parseFloat(process.env.MAX_RAM) : 0.8;
-const CACHE_DURATION = process.env.SYS_INFO_MAX_CACHE_DURATION ? parseFloat(process.env.SYS_INFO_MAX_CACHE_DURATION) : 150;
-
+const CACHE_DURATION = process.env.SYS_INFO_MAX_CACHE_DURATION
+ ? parseFloat(process.env.SYS_INFO_MAX_CACHE_DURATION)
+ : 150;
class SystemMonitor {
- private static instance: SystemMonitor;
- private static instanceMutex = new Mutex();
+ private static instance: SystemMonitor;
+ private static instanceMutex = new Mutex();
- private cpuUsageCache: number | null = null;
- private memoryUsageCache: number | null = null;
- private lastCpuCheck: number = 0;
- private lastMemoryCheck: number = 0;
-
- // Variables for CPU usage calculation
- private previousCpuUsage: number = 0;
- private previousTime: number = Date.now();
+ private cpuUsageCache: number | null = null;
+ private memoryUsageCache: number | null = null;
+ private lastCpuCheck: number = 0;
+ private lastMemoryCheck: number = 0;
- private constructor() {}
+ // Variables for CPU usage calculation
+ private previousCpuUsage: number = 0;
+ private previousTime: number = Date.now();
- public static async getInstance(): Promise {
- if (SystemMonitor.instance) {
- return SystemMonitor.instance;
- }
-
- await this.instanceMutex.runExclusive(async () => {
- if (!SystemMonitor.instance) {
- SystemMonitor.instance = new SystemMonitor();
- }
- });
-
- return SystemMonitor.instance;
+ private constructor() {}
+
+ public static async getInstance(): Promise {
+ if (SystemMonitor.instance) {
+ return SystemMonitor.instance;
}
- public async checkMemoryUsage() {
- if (IS_KUBERNETES) {
- return this._checkMemoryUsageKubernetes();
- }
- return this._checkMemoryUsage();
+ await this.instanceMutex.runExclusive(async () => {
+ if (!SystemMonitor.instance) {
+ SystemMonitor.instance = new SystemMonitor();
+ }
+ });
+
+ return SystemMonitor.instance;
+ }
+
+ public async checkMemoryUsage() {
+ if (IS_KUBERNETES) {
+ return this._checkMemoryUsageKubernetes();
+ }
+ return this._checkMemoryUsage();
+ }
+
+ private readMemoryCurrent(): number {
+ const data = fs.readFileSync("/sys/fs/cgroup/memory.current", "utf8");
+ return parseInt(data.trim(), 10);
+ }
+
+ private readMemoryMax(): number {
+ const data = fs.readFileSync("/sys/fs/cgroup/memory.max", "utf8").trim();
+ if (data === "max") {
+ return Infinity;
+ }
+ return parseInt(data, 10);
+ }
+ private async _checkMemoryUsageKubernetes() {
+ try {
+ const currentMemoryUsage = this.readMemoryCurrent();
+ const memoryLimit = this.readMemoryMax();
+
+ let memoryUsagePercentage: number;
+
+ if (memoryLimit === Infinity) {
+ // No memory limit set; use total system memory
+ const totalMemory = os.totalmem();
+ memoryUsagePercentage = currentMemoryUsage / totalMemory;
+ } else {
+ memoryUsagePercentage = currentMemoryUsage / memoryLimit;
+ }
+
+ // console.log("Memory usage:", memoryUsagePercentage);
+
+ return memoryUsagePercentage;
+ } catch (error) {
+ logger.error(`Error calculating memory usage: ${error}`);
+ return 0; // Fallback to 0% usage
+ }
+ }
+
+ private async _checkMemoryUsage() {
+ const now = Date.now();
+ if (
+ this.memoryUsageCache !== null &&
+ now - this.lastMemoryCheck < CACHE_DURATION
+ ) {
+ return this.memoryUsageCache;
}
+ const memoryData = await si.mem();
+ const totalMemory = memoryData.total;
+ const availableMemory = memoryData.available;
+ const usedMemory = totalMemory - availableMemory;
+ const usedMemoryPercentage = usedMemory / totalMemory;
- private readMemoryCurrent(): number {
- const data = fs.readFileSync('/sys/fs/cgroup/memory.current', 'utf8');
- return parseInt(data.trim(), 10);
+ this.memoryUsageCache = usedMemoryPercentage;
+ this.lastMemoryCheck = now;
+
+ return usedMemoryPercentage;
+ }
+
+ public async checkCpuUsage() {
+ if (IS_KUBERNETES) {
+ return this._checkCpuUsageKubernetes();
+ }
+ return this._checkCpuUsage();
+ }
+ private readCpuUsage(): number {
+ const data = fs.readFileSync("/sys/fs/cgroup/cpu.stat", "utf8");
+ const match = data.match(/^usage_usec (\d+)$/m);
+ if (match) {
+ return parseInt(match[1], 10);
+ }
+ throw new Error("Could not read usage_usec from cpu.stat");
+ }
+
+ private getNumberOfCPUs(): number {
+ let cpus: number[] = [];
+ try {
+ const cpusetPath = "/sys/fs/cgroup/cpuset.cpus.effective";
+ const data = fs.readFileSync(cpusetPath, "utf8").trim();
+
+ if (!data) {
+ throw new Error(`${cpusetPath} is empty.`);
+ }
+
+ cpus = this.parseCpuList(data);
+
+ if (cpus.length === 0) {
+ throw new Error("No CPUs found in cpuset.cpus.effective");
+ }
+ } catch (error) {
+ logger.warn(
+ `Unable to read cpuset.cpus.effective, defaulting to OS CPUs: ${error}`
+ );
+ cpus = os.cpus().map((cpu, index) => index);
+ }
+ return cpus.length;
+ }
+
+ private parseCpuList(cpuList: string): number[] {
+ const ranges = cpuList.split(",");
+ const cpus: number[] = [];
+ ranges.forEach((range) => {
+ const [startStr, endStr] = range.split("-");
+ const start = parseInt(startStr, 10);
+ const end = endStr !== undefined ? parseInt(endStr, 10) : start;
+ for (let i = start; i <= end; i++) {
+ cpus.push(i);
+ }
+ });
+ return cpus;
+ }
+ private async _checkCpuUsageKubernetes() {
+ try {
+ const usage = this.readCpuUsage(); // In microseconds (µs)
+ const now = Date.now();
+
+ // Check if it's the first run
+ if (this.previousCpuUsage === 0) {
+ // Initialize previous values
+ this.previousCpuUsage = usage;
+ this.previousTime = now;
+ // Return 0% CPU usage on first run
+ return 0;
+ }
+
+ const deltaUsage = usage - this.previousCpuUsage; // In µs
+ const deltaTime = (now - this.previousTime) * 1000; // Convert ms to µs
+
+ const numCPUs = this.getNumberOfCPUs(); // Get the number of CPUs
+
+ // Calculate the CPU usage percentage and normalize by the number of CPUs
+ const cpuUsagePercentage = deltaUsage / deltaTime / numCPUs;
+
+ // Update previous values
+ this.previousCpuUsage = usage;
+ this.previousTime = now;
+
+ // console.log("CPU usage:", cpuUsagePercentage);
+
+ return cpuUsagePercentage;
+ } catch (error) {
+ logger.error(`Error calculating CPU usage: ${error}`);
+ return 0; // Fallback to 0% usage
+ }
+ }
+
+ private async _checkCpuUsage() {
+ const now = Date.now();
+ if (
+ this.cpuUsageCache !== null &&
+ now - this.lastCpuCheck < CACHE_DURATION
+ ) {
+ return this.cpuUsageCache;
}
- private readMemoryMax(): number {
- const data = fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
- if (data === 'max') {
- return Infinity;
- }
- return parseInt(data, 10);
- }
- private async _checkMemoryUsageKubernetes() {
- try {
- const currentMemoryUsage = this.readMemoryCurrent();
- const memoryLimit = this.readMemoryMax();
+ const cpuData = await si.currentLoad();
+ const cpuLoad = cpuData.currentLoad / 100;
- let memoryUsagePercentage: number;
+ this.cpuUsageCache = cpuLoad;
+ this.lastCpuCheck = now;
- if (memoryLimit === Infinity) {
- // No memory limit set; use total system memory
- const totalMemory = os.totalmem();
- memoryUsagePercentage = currentMemoryUsage / totalMemory;
- } else {
- memoryUsagePercentage = currentMemoryUsage / memoryLimit;
- }
+ return cpuLoad;
+ }
- // console.log("Memory usage:", memoryUsagePercentage);
+ public async acceptConnection() {
+ const cpuUsage = await this.checkCpuUsage();
+ const memoryUsage = await this.checkMemoryUsage();
- return memoryUsagePercentage;
- } catch (error) {
- logger.error(`Error calculating memory usage: ${error}`);
- return 0; // Fallback to 0% usage
- }
- }
+ return cpuUsage < MAX_CPU && memoryUsage < MAX_RAM;
+ }
- private async _checkMemoryUsage() {
- const now = Date.now();
- if (this.memoryUsageCache !== null && (now - this.lastMemoryCheck) < CACHE_DURATION) {
- return this.memoryUsageCache;
- }
-
- const memoryData = await si.mem();
- const totalMemory = memoryData.total;
- const availableMemory = memoryData.available;
- const usedMemory = totalMemory - availableMemory;
- const usedMemoryPercentage = (usedMemory / totalMemory);
-
- this.memoryUsageCache = usedMemoryPercentage;
- this.lastMemoryCheck = now;
-
- return usedMemoryPercentage;
- }
-
- public async checkCpuUsage() {
- if (IS_KUBERNETES) {
- return this._checkCpuUsageKubernetes();
- }
- return this._checkCpuUsage();
- }
- private readCpuUsage(): number {
- const data = fs.readFileSync('/sys/fs/cgroup/cpu.stat', 'utf8');
- const match = data.match(/^usage_usec (\d+)$/m);
- if (match) {
- return parseInt(match[1], 10);
- }
- throw new Error('Could not read usage_usec from cpu.stat');
- }
-
-
- private getNumberOfCPUs(): number {
- let cpus: number[] = [];
- try {
- const cpusetPath = '/sys/fs/cgroup/cpuset.cpus.effective';
- const data = fs.readFileSync(cpusetPath, 'utf8').trim();
-
- if (!data) {
- throw new Error(`${cpusetPath} is empty.`);
- }
-
- cpus = this.parseCpuList(data);
-
- if (cpus.length === 0) {
- throw new Error('No CPUs found in cpuset.cpus.effective');
- }
- } catch (error) {
- logger.warn(`Unable to read cpuset.cpus.effective, defaulting to OS CPUs: ${error}`);
- cpus = os.cpus().map((cpu, index) => index);
- }
- return cpus.length;
- }
-
-
- private parseCpuList(cpuList: string): number[] {
- const ranges = cpuList.split(',');
- const cpus: number[] = [];
- ranges.forEach((range) => {
- const [startStr, endStr] = range.split('-');
- const start = parseInt(startStr, 10);
- const end = endStr !== undefined ? parseInt(endStr, 10) : start;
- for (let i = start; i <= end; i++) {
- cpus.push(i);
- }
- });
- return cpus;
- }
- private async _checkCpuUsageKubernetes() {
- try {
- const usage = this.readCpuUsage(); // In microseconds (µs)
- const now = Date.now();
-
- // Check if it's the first run
- if (this.previousCpuUsage === 0) {
- // Initialize previous values
- this.previousCpuUsage = usage;
- this.previousTime = now;
- // Return 0% CPU usage on first run
- return 0;
- }
-
- const deltaUsage = usage - this.previousCpuUsage; // In µs
- const deltaTime = (now - this.previousTime) * 1000; // Convert ms to µs
-
- const numCPUs = this.getNumberOfCPUs(); // Get the number of CPUs
-
- // Calculate the CPU usage percentage and normalize by the number of CPUs
- const cpuUsagePercentage = (deltaUsage / deltaTime) / numCPUs;
-
- // Update previous values
- this.previousCpuUsage = usage;
- this.previousTime = now;
-
- // console.log("CPU usage:", cpuUsagePercentage);
-
- return cpuUsagePercentage;
- } catch (error) {
- logger.error(`Error calculating CPU usage: ${error}`);
- return 0; // Fallback to 0% usage
- }
- }
-
-
- private async _checkCpuUsage() {
- const now = Date.now();
- if (this.cpuUsageCache !== null && (now - this.lastCpuCheck) < CACHE_DURATION) {
- return this.cpuUsageCache;
- }
-
- const cpuData = await si.currentLoad();
- const cpuLoad = cpuData.currentLoad / 100;
-
- this.cpuUsageCache = cpuLoad;
- this.lastCpuCheck = now;
-
- return cpuLoad;
- }
-
- public async acceptConnection() {
- const cpuUsage = await this.checkCpuUsage();
- const memoryUsage = await this.checkMemoryUsage();
-
- return cpuUsage < MAX_CPU && memoryUsage < MAX_RAM;
- }
-
- public clearCache() {
- this.cpuUsageCache = null;
- this.memoryUsageCache = null;
- this.lastCpuCheck = 0;
- this.lastMemoryCheck = 0;
- }
+ public clearCache() {
+ this.cpuUsageCache = null;
+ this.memoryUsageCache = null;
+ this.lastCpuCheck = 0;
+ this.lastMemoryCheck = 0;
+ }
}
-export default SystemMonitor.getInstance();
\ No newline at end of file
+export default SystemMonitor.getInstance();
diff --git a/apps/api/src/services/webhook.ts b/apps/api/src/services/webhook.ts
index 7840484d..dfee11f6 100644
--- a/apps/api/src/services/webhook.ts
+++ b/apps/api/src/services/webhook.ts
@@ -22,7 +22,9 @@ export const callWebhook = async (
id
);
const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === "true";
- let webhookUrl = specified ?? (selfHostedUrl ? webhookSchema.parse({ url: selfHostedUrl }) : undefined);
+ let webhookUrl =
+ specified ??
+ (selfHostedUrl ? webhookSchema.parse({ url: selfHostedUrl }) : undefined);
// Only fetch the webhook URL from the database if the self-hosted webhook URL and specified webhook are not set
// and the USE_DB_AUTHENTICATION environment variable is set to true
@@ -46,7 +48,14 @@ export const callWebhook = async (
webhookUrl = webhooksData[0].url;
}
- logger.debug("Calling webhook...", { webhookUrl, teamId, specified, v1, eventType, awaitWebhook });
+ logger.debug("Calling webhook...", {
+ webhookUrl,
+ teamId,
+ specified,
+ v1,
+ eventType,
+ awaitWebhook
+ });
if (!webhookUrl) {
return null;
@@ -61,14 +70,12 @@ export const callWebhook = async (
) {
for (let i = 0; i < data.result.links.length; i++) {
if (v1) {
- dataToSend.push(
- data.result.links[i].content
- );
+ dataToSend.push(data.result.links[i].content);
} else {
dataToSend.push({
content: data.result.links[i].content.content,
markdown: data.result.links[i].content.markdown,
- metadata: data.result.links[i].content.metadata,
+ metadata: data.result.links[i].content.metadata
});
}
}
@@ -82,23 +89,23 @@ export const callWebhook = async (
success: !v1
? data.success
: eventType === "crawl.page"
- ? data.success
- : true,
+ ? data.success
+ : true,
type: eventType,
[v1 ? "id" : "jobId"]: id,
data: dataToSend,
error: !v1
? data?.error || undefined
: eventType === "crawl.page"
- ? data?.error || undefined
- : undefined,
+ ? data?.error || undefined
+ : undefined
},
{
headers: {
"Content-Type": "application/json",
- ...webhookUrl.headers,
+ ...webhookUrl.headers
},
- timeout: v1 ? 10000 : 30000, // 10 seconds timeout (v1)
+ timeout: v1 ? 10000 : 30000 // 10 seconds timeout (v1)
}
);
} catch (error) {
@@ -114,22 +121,22 @@ export const callWebhook = async (
success: !v1
? data.success
: eventType === "crawl.page"
- ? data.success
- : true,
+ ? data.success
+ : true,
type: eventType,
[v1 ? "id" : "jobId"]: id,
data: dataToSend,
error: !v1
? data?.error || undefined
: eventType === "crawl.page"
- ? data?.error || undefined
- : undefined,
+ ? data?.error || undefined
+ : undefined
},
{
headers: {
"Content-Type": "application/json",
- ...webhookUrl.headers,
- },
+ ...webhookUrl.headers
+ }
}
)
.catch((error) => {
diff --git a/apps/api/src/strings.ts b/apps/api/src/strings.ts
index 8edc57f1..d5672b82 100644
--- a/apps/api/src/strings.ts
+++ b/apps/api/src/strings.ts
@@ -1,4 +1,4 @@
export const errorNoResults =
"No results found, please check the URL or contact us at help@mendable.ai to file a ticket.";
-export const clientSideError = "client-side exception has occurred"
\ No newline at end of file
+export const clientSideError = "client-side exception has occurred";
diff --git a/apps/api/src/supabase_types.ts b/apps/api/src/supabase_types.ts
index 00b2efbb..8f9e1b64 100644
--- a/apps/api/src/supabase_types.ts
+++ b/apps/api/src/supabase_types.ts
@@ -40,7 +40,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
company: {
@@ -77,7 +77,7 @@ export interface Database {
columns: ["pricing_plan_id"];
referencedRelation: "pricing_plan";
referencedColumns: ["id"];
- },
+ }
];
};
constants: {
@@ -126,7 +126,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
customers: {
@@ -157,7 +157,7 @@ export interface Database {
columns: ["user_id"];
referencedRelation: "users";
referencedColumns: ["id"];
- },
+ }
];
};
data: {
@@ -236,7 +236,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
data_partitioned: {
@@ -390,7 +390,7 @@ export interface Database {
columns: ["company_id"];
referencedRelation: "company";
referencedColumns: ["company_id"];
- },
+ }
];
};
message: {
@@ -439,7 +439,7 @@ export interface Database {
columns: ["conversation_id"];
referencedRelation: "conversation";
referencedColumns: ["conversation_id"];
- },
+ }
];
};
model_configuration: {
@@ -479,7 +479,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
monthly_message_counts: {
@@ -507,7 +507,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
prices: {
@@ -560,7 +560,7 @@ export interface Database {
columns: ["product_id"];
referencedRelation: "products";
referencedColumns: ["id"];
- },
+ }
];
};
pricing_plan: {
@@ -747,7 +747,7 @@ export interface Database {
columns: ["user_id"];
referencedRelation: "users";
referencedColumns: ["id"];
- },
+ }
];
};
suggested_questions: {
@@ -775,7 +775,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
user_notifications: {
@@ -821,7 +821,7 @@ export interface Database {
columns: ["user_id"];
referencedRelation: "users";
referencedColumns: ["id"];
- },
+ }
];
};
users: {
@@ -864,7 +864,7 @@ export interface Database {
columns: ["id"];
referencedRelation: "users";
referencedColumns: ["id"];
- },
+ }
];
};
z_testcomp_92511: {
@@ -934,7 +934,7 @@ export interface Database {
columns: ["project_id"];
referencedRelation: "mendable_project";
referencedColumns: ["id"];
- },
+ }
];
};
};
diff --git a/apps/api/src/types.ts b/apps/api/src/types.ts
index bf7d2248..cfae8f23 100644
--- a/apps/api/src/types.ts
+++ b/apps/api/src/types.ts
@@ -1,5 +1,10 @@
import { z } from "zod";
-import { AuthCreditUsageChunk, ScrapeOptions, Document as V1Document, webhookSchema } from "./controllers/v1/types";
+import {
+ AuthCreditUsageChunk,
+ ScrapeOptions,
+ Document as V1Document,
+ webhookSchema
+} from "./controllers/v1/types";
import { ExtractorOptions, Document } from "./lib/entities";
import { InternalOptions } from "./scraper/scrapeURL";
@@ -52,13 +57,15 @@ export interface RunWebScraperParams {
is_scrape?: boolean;
}
-export type RunWebScraperResult = {
- success: false;
- error: Error;
-} | {
- success: true;
- document: V1Document;
-}
+export type RunWebScraperResult =
+ | {
+ success: false;
+ error: Error;
+ }
+ | {
+ success: true;
+ document: V1Document;
+ };
export interface FirecrawlJob {
job_id?: string;
@@ -73,8 +80,8 @@ export interface FirecrawlJob {
crawlerOptions?: any;
scrapeOptions?: any;
origin: string;
- num_tokens?: number,
- retry?: boolean,
+ num_tokens?: number;
+ retry?: boolean;
crawl_id?: string;
}
@@ -92,7 +99,6 @@ export interface FirecrawlCrawlResponse {
body: {
status: string;
jobId: string;
-
};
error?: string;
}
@@ -101,7 +107,7 @@ export interface FirecrawlCrawlStatusResponse {
statusCode: number;
body: {
status: string;
- data: Document[];
+ data: Document[];
};
error?: string;
}
@@ -121,29 +127,29 @@ export enum RateLimiterMode {
Scrape = "scrape",
Preview = "preview",
Search = "search",
- Map = "map",
-
+ Map = "map"
}
-export type AuthResponse = {
- success: true;
- team_id: string;
- api_key?: string;
- plan?: PlanType;
- chunk: AuthCreditUsageChunk | null;
-} | {
- success: false;
- error: string;
- status: number;
-}
-
+export type AuthResponse =
+ | {
+ success: true;
+ team_id: string;
+ api_key?: string;
+ plan?: PlanType;
+ chunk: AuthCreditUsageChunk | null;
+ }
+ | {
+ success: false;
+ error: string;
+ status: number;
+ };
export enum NotificationType {
APPROACHING_LIMIT = "approachingLimit",
LIMIT_REACHED = "limitReached",
RATE_LIMIT_REACHED = "rateLimitReached",
AUTO_RECHARGE_SUCCESS = "autoRechargeSuccess",
- AUTO_RECHARGE_FAILED = "autoRechargeFailed",
+ AUTO_RECHARGE_FAILED = "autoRechargeFailed"
}
export type ScrapeLog = {
@@ -161,7 +167,7 @@ export type ScrapeLog = {
ipv6_support?: boolean | null;
};
-export type PlanType =
+export type PlanType =
| "starter"
| "standard"
| "scale"
@@ -175,5 +181,11 @@ export type PlanType =
| "free"
| "";
-
-export type WebhookEventType = "crawl.page" | "batch_scrape.page" | "crawl.started" | "batch_scrape.started" | "crawl.completed" | "batch_scrape.completed" | "crawl.failed";
\ No newline at end of file
+export type WebhookEventType =
+ | "crawl.page"
+ | "batch_scrape.page"
+ | "crawl.started"
+ | "batch_scrape.started"
+ | "crawl.completed"
+ | "batch_scrape.completed"
+ | "crawl.failed";