@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 +1 -1
- package/bin/mcp.js +1 -1
- package/bin/serve.js +1 -1
- package/connectors/firecrawl/src/api/client.ts +3 -1
- package/connectors/firecrawl/src/api/scrape-contract.test.ts +64 -0
- package/connectors/firecrawl/src/api/scrape.ts +4 -3
- package/connectors/firecrawl/src/cli/extraction.test.ts +6 -6
- package/connectors/firecrawl/src/cli/extraction.ts +5 -3
- package/connectors/firecrawl/src/types/index.ts +3 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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' | '
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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.
|
|
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: {
|