@hasna/connectors 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -1909,7 +1909,7 @@ var package_default;
1909
1909
  var init_package = __esm(() => {
1910
1910
  package_default = {
1911
1911
  name: "@hasna/connectors",
1912
- version: "1.4.6",
1912
+ version: "1.4.7",
1913
1913
  description: "Open source connector library - Install API connectors with a single command",
1914
1914
  type: "module",
1915
1915
  bin: {
package/bin/mcp.js CHANGED
@@ -5905,7 +5905,7 @@ var package_default;
5905
5905
  var init_package = __esm(() => {
5906
5906
  package_default = {
5907
5907
  name: "@hasna/connectors",
5908
- version: "1.4.6",
5908
+ version: "1.4.7",
5909
5909
  description: "Open source connector library - Install API connectors with a single command",
5910
5910
  type: "module",
5911
5911
  bin: {
package/bin/serve.js CHANGED
@@ -6535,7 +6535,7 @@ var package_default;
6535
6535
  var init_package = __esm(() => {
6536
6536
  package_default = {
6537
6537
  name: "@hasna/connectors",
6538
- version: "1.4.6",
6538
+ version: "1.4.7",
6539
6539
  description: "Open source connector library - Install API connectors with a single command",
6540
6540
  type: "module",
6541
6541
  bin: {
@@ -13,6 +13,7 @@ export interface RequestOptions {
13
13
  export class FirecrawlClient {
14
14
  private readonly apiKey: string;
15
15
  private readonly baseUrl: string;
16
+ private readonly fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
16
17
 
17
18
  constructor(config: FirecrawlConfig) {
18
19
  if (!config.apiKey) {
@@ -20,6 +21,7 @@ export class FirecrawlClient {
20
21
  }
21
22
  this.apiKey = config.apiKey;
22
23
  this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
24
+ this.fetchImpl = config.fetchImpl || fetch;
23
25
  }
24
26
 
25
27
  private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
@@ -64,7 +66,7 @@ export class FirecrawlClient {
64
66
  fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
65
67
  }
66
68
 
67
- const response = await fetch(url, fetchOptions);
69
+ const response = await this.fetchImpl(url, fetchOptions);
68
70
 
69
71
  // Handle 204 No Content
70
72
  if (response.status === 204) {
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { FirecrawlClient } from './client';
3
+ import { ScrapeApi } from './scrape';
4
+
5
+ function contractFetch(requests: Request[], bodies: Record<string, unknown>[]) {
6
+ return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
7
+ const request = new Request(input, init);
8
+ requests.push(request);
9
+ if (request.method !== 'POST' || new URL(request.url).pathname !== '/v1/scrape') {
10
+ return Response.json({ error: 'wrong method or path' }, { status: 404 });
11
+ }
12
+ if (request.headers.get('authorization') !== 'Bearer fixture-key' || request.headers.get('content-type') !== 'application/json') {
13
+ return Response.json({ error: 'wrong headers' }, { status: 401 });
14
+ }
15
+ const body = await request.json() as { formats?: unknown[]; jsonOptions?: unknown; extract?: unknown };
16
+ bodies.push(body);
17
+ const formats = Array.isArray(body.formats) ? body.formats : [];
18
+ const hasJsonOptions = body.jsonOptions !== undefined;
19
+ const hasJsonFormat = formats.includes('json');
20
+ if (body.extract !== undefined || hasJsonOptions !== hasJsonFormat) {
21
+ return Response.json({ success: false, error: 'v1 requires jsonOptions with the json format' }, { status: 400 });
22
+ }
23
+ return Response.json({ success: true, data: { formats, jsonOptions: body.jsonOptions } });
24
+ };
25
+ }
26
+
27
+ describe('Firecrawl v1 scrape extraction contract', () => {
28
+ test('rejects the legacy shape and verifies method, path, headers, and mixed formats', async () => {
29
+ const requests: Request[] = [];
30
+ const bodies: Record<string, unknown>[] = [];
31
+ const client = new FirecrawlClient({ apiKey: 'fixture-key', baseUrl: 'http://fixture/v1', fetchImpl: contractFetch(requests, bodies) });
32
+ await expect(client.post('/scrape', {
33
+ url: 'https://example.com', formats: ['markdown', 'html', 'links'],
34
+ extract: { schema: { type: 'object' }, prompt: 'Extract the title' },
35
+ })).rejects.toMatchObject({ statusCode: 400 });
36
+ const response = await client.post<{ success: boolean; data: { formats: unknown[]; jsonOptions: unknown } }>('/scrape', {
37
+ url: 'https://example.com', formats: ['markdown', 'html', 'links', 'json'],
38
+ jsonOptions: { schema: { type: 'object' }, prompt: 'Extract the title' },
39
+ });
40
+ expect(response.data.formats).toEqual(['markdown', 'html', 'links', 'json']);
41
+ expect(response.data.jsonOptions).toEqual({ schema: { type: 'object' }, prompt: 'Extract the title' });
42
+ expect(requests).toHaveLength(2);
43
+ expect(requests[1].method).toBe('POST');
44
+ expect(new URL(requests[1].url).pathname).toBe('/v1/scrape');
45
+ expect(requests[1].headers.get('authorization')).toBe('Bearer fixture-key');
46
+ expect(requests[1].headers.get('content-type')).toBe('application/json');
47
+ });
48
+
49
+ test('SDK convenience extraction uses jsonOptions and preserves requested content formats', async () => {
50
+ const requests: Request[] = [];
51
+ const bodies: Record<string, unknown>[] = [];
52
+ const client = new FirecrawlClient({ apiKey: 'fixture-key', baseUrl: 'http://fixture/v1', fetchImpl: contractFetch(requests, bodies) });
53
+ const response = await new ScrapeApi(client).scrapeWithExtraction('https://example.com', {
54
+ type: 'object', properties: { title: { type: 'string' } },
55
+ }, 'Extract the title', ['html', 'links']);
56
+ expect(response.success).toBe(true);
57
+ expect(requests).toHaveLength(1);
58
+ const body = bodies[0] as { formats: string[]; jsonOptions: unknown };
59
+ expect(body.formats).toEqual(['markdown', 'html', 'links', 'json']);
60
+ expect(body.jsonOptions).toEqual({
61
+ schema: { type: 'object', properties: { title: { type: 'string' } } }, prompt: 'Extract the title',
62
+ });
63
+ });
64
+ });
@@ -43,11 +43,12 @@ export class ScrapeApi {
43
43
  async scrapeWithExtraction(
44
44
  url: string,
45
45
  schema: Record<string, unknown>,
46
- prompt?: string
46
+ prompt?: string,
47
+ formats: ScrapeFormat[] = ['markdown'],
47
48
  ): Promise<ScrapeResponse> {
48
49
  return this.scrape(url, {
49
- formats: ['markdown'],
50
- extract: {
50
+ formats: Array.from(new Set<ScrapeFormat>(['markdown', ...formats, 'json'])),
51
+ jsonOptions: {
51
52
  schema,
52
53
  prompt,
53
54
  },
@@ -11,8 +11,8 @@ describe('Firecrawl CLI extraction options', () => {
11
11
  writeFileSync(schemaPath, JSON.stringify({ type: 'object', properties: { title: { type: 'string' } } }));
12
12
 
13
13
  expect(buildExtractionOptions(schemaPath, 'Extract the page title')).toEqual({
14
- formats: ['markdown'],
15
- extract: {
14
+ formats: ['markdown', 'json'],
15
+ jsonOptions: {
16
16
  schema: { type: 'object', properties: { title: { type: 'string' } } },
17
17
  prompt: 'Extract the page title',
18
18
  },
@@ -21,15 +21,15 @@ describe('Firecrawl CLI extraction options', () => {
21
21
 
22
22
  test('supports a prompt without a schema file', () => {
23
23
  expect(buildExtractionOptions(undefined, ' Find the publication date ')).toEqual({
24
- formats: ['markdown'],
25
- extract: { schema: undefined, prompt: 'Find the publication date' },
24
+ formats: ['markdown', 'json'],
25
+ jsonOptions: { schema: undefined, prompt: 'Find the publication date' },
26
26
  });
27
27
  });
28
28
 
29
29
  test('preserves requested formats while adding markdown for extraction', () => {
30
30
  expect(buildExtractionOptions(undefined, 'Extract the title', ['html', 'screenshot@fullPage'])).toEqual({
31
- formats: ['markdown', 'html', 'screenshot@fullPage'],
32
- extract: { schema: undefined, prompt: 'Extract the title' },
31
+ formats: ['markdown', 'html', 'screenshot@fullPage', 'json'],
32
+ jsonOptions: { schema: undefined, prompt: 'Extract the title' },
33
33
  });
34
34
  });
35
35
 
@@ -22,13 +22,15 @@ export function buildExtractionOptions(
22
22
  schemaPath?: string,
23
23
  prompt?: string,
24
24
  formats: ScrapeFormat[] = ['markdown'],
25
- ): Pick<ScrapeRequest, 'formats' | 'extract'> | undefined {
25
+ ): Pick<ScrapeRequest, 'formats' | 'jsonOptions'> | undefined {
26
26
  const normalizedPrompt = prompt?.trim();
27
27
  if (!schemaPath && !normalizedPrompt) return undefined;
28
28
 
29
29
  return {
30
- formats: Array.from(new Set<ScrapeFormat>(['markdown', ...formats])),
31
- extract: {
30
+ // Firecrawl's v1 scrape contract requires jsonOptions with the json
31
+ // format. Keep the requested content formats as well.
32
+ formats: Array.from(new Set<ScrapeFormat>(['markdown', ...formats, 'json'])),
33
+ jsonOptions: {
32
34
  schema: schemaPath ? readExtractionSchema(schemaPath) : undefined,
33
35
  prompt: normalizedPrompt,
34
36
  },
@@ -7,6 +7,7 @@
7
7
  export interface FirecrawlConfig {
8
8
  apiKey: string;
9
9
  baseUrl?: string; // Override default base URL (https://api.firecrawl.dev/v1)
10
+ fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; // Injectable transport for isolated contract tests
10
11
  }
11
12
 
12
13
  // ============================================
@@ -15,7 +16,7 @@ export interface FirecrawlConfig {
15
16
 
16
17
  export type OutputFormat = 'json' | 'pretty';
17
18
 
18
- export type ScrapeFormat = 'markdown' | 'html' | 'rawHtml' | 'links' | 'screenshot' | 'screenshot@fullPage';
19
+ export type ScrapeFormat = 'markdown' | 'html' | 'rawHtml' | 'links' | 'json' | 'screenshot' | 'screenshot@fullPage';
19
20
 
20
21
  export interface Action {
21
22
  type: 'wait' | 'click' | 'write' | 'press' | 'screenshot' | 'scroll';
@@ -43,7 +44,7 @@ export interface ScrapeRequest {
43
44
  skipTlsVerification?: boolean;
44
45
  timeout?: number;
45
46
  actions?: Action[];
46
- extract?: {
47
+ jsonOptions?: {
47
48
  schema?: Record<string, unknown>;
48
49
  systemPrompt?: string;
49
50
  prompt?: string;
package/dist/index.js CHANGED
@@ -5862,7 +5862,7 @@ function extractGoogleError(body) {
5862
5862
  // package.json
5863
5863
  var package_default = {
5864
5864
  name: "@hasna/connectors",
5865
- version: "1.4.6",
5865
+ version: "1.4.7",
5866
5866
  description: "Open source connector library - Install API connectors with a single command",
5867
5867
  type: "module",
5868
5868
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/connectors",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Open source connector library - Install API connectors with a single command",
5
5
  "type": "module",
6
6
  "bin": {