@mendable/firecrawl-js
Version:
JavaScript SDK for Firecrawl API
215 lines (203 loc) • 7.86 kB
text/typescript
import {
type ChangeTrackingFormat,
type FormatOption,
type JsonFormat,
type ParseFormatOption,
type ParseOptions,
type QuestionFormat,
type HighlightsFormat,
type QueryFormat,
type ScrapeOptions,
type ScreenshotFormat,
} from "../types";
import { isZodSchema, zodSchemaToJsonSchema, looksLikeZodShape } from "../../utils/zodSchemaToJson";
export function ensureValidFormats(formats?: FormatOption[]): void {
if (!formats) return;
for (const fmt of formats) {
if (typeof fmt === "string") {
if (fmt === "json") {
throw new Error("json format must be an object with { type: 'json', prompt, schema }");
}
continue;
}
if ((fmt as JsonFormat).type === "json") {
const j = fmt as JsonFormat;
if (!j.prompt && !j.schema) {
throw new Error("json format requires either 'prompt' or 'schema' (or both)");
}
const maybeSchema = j.schema;
if (isZodSchema(maybeSchema)) {
(j as any).schema = zodSchemaToJsonSchema(maybeSchema);
} else if (looksLikeZodShape(maybeSchema)) {
throw new Error(
"json format schema appears to be a Zod schema's .shape property. " +
"Pass the Zod schema directly (e.g., `schema: MySchema`) instead of `schema: MySchema.shape`. " +
"The SDK will automatically convert Zod schemas to JSON Schema format."
);
}
continue;
}
if ((fmt as ChangeTrackingFormat).type === "changeTracking") {
const ct = fmt as ChangeTrackingFormat;
const maybeSchema = ct.schema;
if (isZodSchema(maybeSchema)) {
(ct as any).schema = zodSchemaToJsonSchema(maybeSchema);
} else if (looksLikeZodShape(maybeSchema)) {
throw new Error(
"changeTracking format schema appears to be a Zod schema's .shape property. " +
"Pass the Zod schema directly (e.g., `schema: MySchema`) instead of `schema: MySchema.shape`. " +
"The SDK will automatically convert Zod schemas to JSON Schema format."
);
}
continue;
}
if ((fmt as QuestionFormat).type === "question") {
const q = fmt as QuestionFormat;
if (typeof q.question !== "string" || q.question.trim().length === 0) {
throw new Error("question format requires a non-empty 'question' string");
}
continue;
}
if ((fmt as HighlightsFormat).type === "highlights") {
const h = fmt as HighlightsFormat;
if (typeof h.query !== "string" || h.query.trim().length === 0) {
throw new Error("highlights format requires a non-empty 'query' string");
}
continue;
}
if ((fmt as QueryFormat).type === "query") {
const q = fmt as QueryFormat;
if (typeof q.prompt !== "string" || q.prompt.trim().length === 0) {
throw new Error("query format requires a non-empty 'prompt' string");
}
if (q.mode != null && q.mode !== "freeform" && q.mode !== "directQuote") {
throw new Error("query format mode must be 'freeform' or 'directQuote'");
}
continue;
}
if ((fmt as ScreenshotFormat).type === "screenshot") {
// no-op; already camelCase; validate numeric fields if present
const s = fmt as ScreenshotFormat;
if (s.quality != null && (typeof s.quality !== "number" || s.quality < 0)) {
throw new Error("screenshot.quality must be a non-negative number");
}
}
}
}
export function ensureValidScrapeOptions(options?: ScrapeOptions): void {
if (!options) return;
if (options.timeout != null && options.timeout <= 0) {
throw new Error("timeout must be positive");
}
if (options.waitFor != null && options.waitFor < 0) {
throw new Error("waitFor must be non-negative");
}
ensureValidFormats(options.formats);
}
export function ensureValidParseFormats(formats?: ParseFormatOption[]): void {
if (!formats) return;
for (const fmt of formats) {
if (typeof fmt === "string") {
if (fmt === "json") {
throw new Error("json format must be an object with { type: 'json', prompt, schema }");
}
if (fmt === "screenshot") {
throw new Error("parse does not support screenshot format");
}
if (fmt === "changeTracking") {
throw new Error("parse does not support changeTracking format");
}
if (fmt === "branding") {
throw new Error("parse does not support branding format");
}
if (fmt === "audio" || fmt === "video") {
throw new Error(`parse does not support ${fmt} format`);
}
continue;
}
const type = (fmt as any).type;
if (type === "changeTracking") {
throw new Error("parse does not support changeTracking format");
}
if (type === "screenshot") {
throw new Error("parse does not support screenshot format");
}
if (type === "branding") {
throw new Error("parse does not support branding format");
}
if (type === "audio" || type === "video") {
throw new Error(`parse does not support ${type} format`);
}
if ((fmt as JsonFormat).type === "json") {
const j = fmt as JsonFormat;
if (!j.prompt && !j.schema) {
throw new Error("json format requires either 'prompt' or 'schema' (or both)");
}
const maybeSchema = j.schema;
if (isZodSchema(maybeSchema)) {
(j as any).schema = zodSchemaToJsonSchema(maybeSchema);
} else if (looksLikeZodShape(maybeSchema)) {
throw new Error(
"json format schema appears to be a Zod schema's .shape property. " +
"Pass the Zod schema directly (e.g., `schema: MySchema`) instead of `schema: MySchema.shape`. " +
"The SDK will automatically convert Zod schemas to JSON Schema format."
);
}
continue;
}
if ((fmt as QuestionFormat).type === "question") {
const q = fmt as QuestionFormat;
if (typeof q.question !== "string" || q.question.trim().length === 0) {
throw new Error("question format requires a non-empty 'question' string");
}
continue;
}
if ((fmt as HighlightsFormat).type === "highlights") {
const h = fmt as HighlightsFormat;
if (typeof h.query !== "string" || h.query.trim().length === 0) {
throw new Error("highlights format requires a non-empty 'query' string");
}
continue;
}
if ((fmt as QueryFormat).type === "query") {
const q = fmt as QueryFormat;
if (typeof q.prompt !== "string" || q.prompt.trim().length === 0) {
throw new Error("query format requires a non-empty 'prompt' string");
}
if (q.mode != null && q.mode !== "freeform" && q.mode !== "directQuote") {
throw new Error("query format mode must be 'freeform' or 'directQuote'");
}
}
}
}
export function ensureValidParseOptions(options?: ParseOptions): void {
if (!options) return;
if (options.timeout != null && options.timeout <= 0) {
throw new Error("timeout must be positive");
}
const raw = options as Record<string, unknown>;
if (raw.waitFor !== undefined) {
throw new Error("parse does not support waitFor");
}
if (raw.actions !== undefined) {
throw new Error("parse does not support actions");
}
if (raw.location !== undefined) {
throw new Error("parse does not support location overrides");
}
if (raw.mobile !== undefined) {
throw new Error("parse does not support mobile rendering");
}
if (
raw.maxAge !== undefined ||
raw.minAge !== undefined ||
raw.storeInCache !== undefined ||
raw.lockdown !== undefined
) {
throw new Error("parse does not support cache/index options");
}
if (raw.proxy !== undefined && raw.proxy !== "basic" && raw.proxy !== "auto") {
throw new Error("parse only supports proxy values of 'basic' or 'auto'");
}
ensureValidParseFormats(options.formats);
}