@bluefields/fetcher 0.2.0
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/LICENSE +661 -0
- package/dist/asset-block-measure.d.ts +14 -0
- package/dist/asset-block-measure.js +82 -0
- package/dist/asset-block-measure.js.map +1 -0
- package/dist/browser-pool.d.ts +65 -0
- package/dist/browser-pool.js +161 -0
- package/dist/browser-pool.js.map +1 -0
- package/dist/cost.d.ts +51 -0
- package/dist/cost.js +54 -0
- package/dist/cost.js.map +1 -0
- package/dist/fetcher-brightdata.d.ts +52 -0
- package/dist/fetcher-brightdata.js +135 -0
- package/dist/fetcher-brightdata.js.map +1 -0
- package/dist/fetcher-escalating.d.ts +100 -0
- package/dist/fetcher-escalating.js +489 -0
- package/dist/fetcher-escalating.js.map +1 -0
- package/dist/fetcher-http.d.ts +81 -0
- package/dist/fetcher-http.js +395 -0
- package/dist/fetcher-http.js.map +1 -0
- package/dist/fetcher-patchright.d.ts +212 -0
- package/dist/fetcher-patchright.js +505 -0
- package/dist/fetcher-patchright.js.map +1 -0
- package/dist/fetcher-robots-gate.d.ts +60 -0
- package/dist/fetcher-robots-gate.js +87 -0
- package/dist/fetcher-robots-gate.js.map +1 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-options.d.ts +59 -0
- package/dist/internal-options.js +16 -0
- package/dist/internal-options.js.map +1 -0
- package/dist/perf-harness.d.ts +105 -0
- package/dist/perf-harness.js +176 -0
- package/dist/perf-harness.js.map +1 -0
- package/dist/storage-memory.d.ts +18 -0
- package/dist/storage-memory.js +45 -0
- package/dist/storage-memory.js.map +1 -0
- package/dist/storage-r2.d.ts +44 -0
- package/dist/storage-r2.js +209 -0
- package/dist/storage-r2.js.map +1 -0
- package/dist/types.d.ts +231 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetcher + storage adapter contracts (chunk 8).
|
|
3
|
+
*
|
|
4
|
+
* The fetcher returns raw HTML + metadata. The storage adapter persists
|
|
5
|
+
* the bytes content-addressed in R2 (Review #2). Both have an in-memory
|
|
6
|
+
* test implementation and an env-gated production implementation.
|
|
7
|
+
*
|
|
8
|
+
* Per the four-layer rule: the fetcher returns raw bytes +
|
|
9
|
+
* metadata, nothing more. It does NOT extract, diff, or notify.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* What to wait for before considering the page "ready" for content
|
|
13
|
+
* extraction. Only honored by browser-based adapters (Patchright);
|
|
14
|
+
* HTTP adapter ignores this and returns as soon as the response body
|
|
15
|
+
* arrives.
|
|
16
|
+
*/
|
|
17
|
+
export type WaitCondition =
|
|
18
|
+
/** Wait until `document.readyState === 'complete'` (default for browser). */
|
|
19
|
+
{
|
|
20
|
+
type: 'load';
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Wait until network is idle for 500ms (Patchright `networkidle`). */
|
|
24
|
+
| {
|
|
25
|
+
type: 'networkIdle';
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Wait for a CSS selector to appear in the DOM. */
|
|
29
|
+
| {
|
|
30
|
+
type: 'selector';
|
|
31
|
+
selector: string;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Wait a fixed amount of time after navigation. */
|
|
35
|
+
| {
|
|
36
|
+
type: 'timeout';
|
|
37
|
+
ms: number;
|
|
38
|
+
};
|
|
39
|
+
export interface RequestCookie {
|
|
40
|
+
name: string;
|
|
41
|
+
value: string;
|
|
42
|
+
domain?: string;
|
|
43
|
+
path?: string;
|
|
44
|
+
expires?: number;
|
|
45
|
+
httpOnly?: boolean;
|
|
46
|
+
secure?: boolean;
|
|
47
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Steps run inside the browser between `goto` + `waitFor` and content
|
|
51
|
+
* extraction. Required for sites that need interaction (cookie banners,
|
|
52
|
+
* "load more" buttons, login forms, infinite scroll). Browser-only —
|
|
53
|
+
* the HTTP fetcher rejects when actions are present.
|
|
54
|
+
*
|
|
55
|
+
* Execution is sequential; one failure short-circuits and surfaces the
|
|
56
|
+
* action index in the error. Each action has its own timeoutMs where
|
|
57
|
+
* applicable; the overall request timeout still applies.
|
|
58
|
+
*/
|
|
59
|
+
export type PageAction =
|
|
60
|
+
/** Sleep for `milliseconds`, or wait for `selector` to appear. */
|
|
61
|
+
{
|
|
62
|
+
type: 'wait';
|
|
63
|
+
milliseconds?: number;
|
|
64
|
+
selector?: string;
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** Click a CSS selector. */
|
|
68
|
+
| {
|
|
69
|
+
type: 'click';
|
|
70
|
+
selector: string;
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Type `text` into the page. When `selector` is provided, fills that
|
|
75
|
+
* element directly (replaces its value); otherwise types into whatever
|
|
76
|
+
* is focused — pair with a prior `click` action.
|
|
77
|
+
*/
|
|
78
|
+
| {
|
|
79
|
+
type: 'write';
|
|
80
|
+
text: string;
|
|
81
|
+
selector?: string;
|
|
82
|
+
}
|
|
83
|
+
/** Press a keyboard key — `Enter`, `Tab`, `Escape`, `ArrowDown`, etc. */
|
|
84
|
+
| {
|
|
85
|
+
type: 'press';
|
|
86
|
+
key: string;
|
|
87
|
+
}
|
|
88
|
+
/** Scroll the viewport. `amount` defaults to one viewport. */
|
|
89
|
+
| {
|
|
90
|
+
type: 'scroll';
|
|
91
|
+
direction: 'up' | 'down';
|
|
92
|
+
amount?: number;
|
|
93
|
+
}
|
|
94
|
+
/** Capture a screenshot at this point in the sequence. */
|
|
95
|
+
| {
|
|
96
|
+
type: 'screenshot';
|
|
97
|
+
fullPage?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/** Capture the current page HTML at this point in the sequence. */
|
|
100
|
+
| {
|
|
101
|
+
type: 'scrape';
|
|
102
|
+
}
|
|
103
|
+
/** Run JavaScript in the page context and capture its return value. */
|
|
104
|
+
| {
|
|
105
|
+
type: 'executeJavascript';
|
|
106
|
+
script: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Results from result-yielding actions (screenshot, scrape,
|
|
110
|
+
* executeJavascript), collected in execution order. Arrays are empty when
|
|
111
|
+
* no such action ran. Surfaced to /scrape callers under `actions`.
|
|
112
|
+
*/
|
|
113
|
+
export interface ActionResults {
|
|
114
|
+
/** Base64-encoded PNG bytes, one entry per `screenshot` action. */
|
|
115
|
+
screenshots: string[];
|
|
116
|
+
/** Page HTML captured by each `scrape` action. */
|
|
117
|
+
scrapes: {
|
|
118
|
+
html: string;
|
|
119
|
+
}[];
|
|
120
|
+
/** Return value of each `executeJavascript` action. */
|
|
121
|
+
javascriptReturns: {
|
|
122
|
+
value: unknown;
|
|
123
|
+
}[];
|
|
124
|
+
}
|
|
125
|
+
export interface FetchOptions {
|
|
126
|
+
/** Hard timeout (ms). Default 30s per Review #4. */
|
|
127
|
+
timeoutMs?: number;
|
|
128
|
+
/** User-Agent string to send. */
|
|
129
|
+
userAgent?: string;
|
|
130
|
+
/** Test seam — override Date.now() for `fetchedAt`. */
|
|
131
|
+
nowFn?: () => Date;
|
|
132
|
+
/**
|
|
133
|
+
* Wait condition before reading page content. Browser adapters honor
|
|
134
|
+
* this; HTTP adapter ignores (returns as soon as response body arrives).
|
|
135
|
+
* Default: `{ type: 'load' }` (browser) or N/A (HTTP).
|
|
136
|
+
*/
|
|
137
|
+
waitFor?: WaitCondition;
|
|
138
|
+
/**
|
|
139
|
+
* Capture a screenshot alongside the HTML. Only browser adapters
|
|
140
|
+
* (Patchright) can do this — HTTP adapter returns `screenshot: null`.
|
|
141
|
+
*/
|
|
142
|
+
captureScreenshot?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Extra HTTP request headers — Authorization, custom UA overrides, etc.
|
|
145
|
+
* `user-agent` here takes precedence over the top-level `userAgent`.
|
|
146
|
+
*/
|
|
147
|
+
headers?: Record<string, string>;
|
|
148
|
+
/**
|
|
149
|
+
* Cookies sent with the request (Phase G). Browser adapters apply via
|
|
150
|
+
* BrowserContext.addCookies; HTTP adapter serializes them into a
|
|
151
|
+
* `Cookie:` header (domain/path filtering matches the request URL).
|
|
152
|
+
*/
|
|
153
|
+
cookies?: RequestCookie[];
|
|
154
|
+
/**
|
|
155
|
+
* Sequential interaction steps run between navigation and content
|
|
156
|
+
* extraction (Phase F). Browser-only — the HTTP fetcher throws when
|
|
157
|
+
* actions are present.
|
|
158
|
+
*/
|
|
159
|
+
actions?: PageAction[];
|
|
160
|
+
}
|
|
161
|
+
export interface FetchResult {
|
|
162
|
+
/** Raw HTML response body. */
|
|
163
|
+
html: string;
|
|
164
|
+
responseStatus: number;
|
|
165
|
+
/** Map of lowercased response header name → value. */
|
|
166
|
+
responseHeaders: Record<string, string>;
|
|
167
|
+
fetchedAt: Date;
|
|
168
|
+
fetchDurationMs: number;
|
|
169
|
+
/** Identifies the fetcher implementation that produced this row. */
|
|
170
|
+
fetcherId: string;
|
|
171
|
+
/** Egress IP of the fetcher (proxy or local depending on adapter). */
|
|
172
|
+
proxyEgressIp: string;
|
|
173
|
+
userAgent: string;
|
|
174
|
+
rawHtmlSizeBytes: number;
|
|
175
|
+
/**
|
|
176
|
+
* Bytes actually transferred over the wire (sum of response
|
|
177
|
+
* Content-Length) — a proxy for metered-egress cost. Browser adapters
|
|
178
|
+
* that abort image/media/font downloads count only what they kept;
|
|
179
|
+
* aborted requests never produce a response, so they're excluded.
|
|
180
|
+
* Optional: adapters that don't measure it leave it undefined.
|
|
181
|
+
*/
|
|
182
|
+
bytesTransferred?: number;
|
|
183
|
+
/**
|
|
184
|
+
* Full-page PNG screenshot bytes (browser adapters only when
|
|
185
|
+
* `captureScreenshot: true`). null otherwise.
|
|
186
|
+
*/
|
|
187
|
+
screenshot: Buffer | null;
|
|
188
|
+
/**
|
|
189
|
+
* Final URL after redirects. Useful for /scrape to surface to
|
|
190
|
+
* customers when the requested URL bounced. Same as the input URL
|
|
191
|
+
* for non-redirecting fetches.
|
|
192
|
+
*/
|
|
193
|
+
finalUrl: string;
|
|
194
|
+
/**
|
|
195
|
+
* Results from result-yielding page actions (screenshot, scrape,
|
|
196
|
+
* executeJavascript). Present only when such actions ran.
|
|
197
|
+
*/
|
|
198
|
+
actionResults?: ActionResults;
|
|
199
|
+
}
|
|
200
|
+
export interface FetcherAdapter {
|
|
201
|
+
fetch(url: string, options?: FetchOptions): Promise<FetchResult>;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A fetcher whose lazy resources (e.g. the Patchright browser pool) can be eagerly
|
|
205
|
+
* initialized at boot so the FIRST customer fetch doesn't pay the setup cost (perf
|
|
206
|
+
* round, L4 — a cold Chromium launch is ~0.5–1.5s of pure tail). `warmUp` is optional
|
|
207
|
+
* and a no-op for adapters with nothing to warm; call it once at app boot,
|
|
208
|
+
* fire-and-forget. A plain `FetcherAdapter` consumer that never calls it is unaffected.
|
|
209
|
+
*/
|
|
210
|
+
export interface WarmableFetcherAdapter extends FetcherAdapter {
|
|
211
|
+
warmUp?(): Promise<void>;
|
|
212
|
+
}
|
|
213
|
+
/** Output of `storeSnapshot`. */
|
|
214
|
+
export interface StoredSnapshot {
|
|
215
|
+
contentHash: string;
|
|
216
|
+
/** Object key — Review #2: `content/<first2-of-sha>/<sha256>`. */
|
|
217
|
+
r2Key: string;
|
|
218
|
+
}
|
|
219
|
+
export interface StorageAdapter {
|
|
220
|
+
/** Persist `content`; idempotent (same content → same key). */
|
|
221
|
+
storeSnapshot(content: string): Promise<StoredSnapshot>;
|
|
222
|
+
/** Retrieve previously-stored content by hash. Throws on miss. */
|
|
223
|
+
getSnapshot(contentHash: string): Promise<string>;
|
|
224
|
+
/** True if the content has already been stored. */
|
|
225
|
+
hasSnapshot(contentHash: string): Promise<boolean>;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Content-addressed key per Review #2: 2-char prefix shard for filesystem
|
|
229
|
+
* fan-out, then the full sha256 hex.
|
|
230
|
+
*/
|
|
231
|
+
export declare function buildR2Key(contentHash: string): string;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Fetcher + storage adapter contracts (chunk 8).
|
|
4
|
+
*
|
|
5
|
+
* The fetcher returns raw HTML + metadata. The storage adapter persists
|
|
6
|
+
* the bytes content-addressed in R2 (Review #2). Both have an in-memory
|
|
7
|
+
* test implementation and an env-gated production implementation.
|
|
8
|
+
*
|
|
9
|
+
* Per the four-layer rule: the fetcher returns raw bytes +
|
|
10
|
+
* metadata, nothing more. It does NOT extract, diff, or notify.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Content-addressed key per Review #2: 2-char prefix shard for filesystem
|
|
14
|
+
* fan-out, then the full sha256 hex.
|
|
15
|
+
*/
|
|
16
|
+
export function buildR2Key(contentHash) {
|
|
17
|
+
if (!/^[0-9a-f]{64}$/.test(contentHash)) {
|
|
18
|
+
throw new Error(`contentHash must be a 64-char lowercase hex string, got ${contentHash}`);
|
|
19
|
+
}
|
|
20
|
+
return `content/${contentHash.slice(0, 2)}/${contentHash}`;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C;;;;;;;;;GASG;AAuLH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC5C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2DAA2D,WAAW,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,WAAW,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC;AAC7D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bluefields/fetcher",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"drizzle-orm": "0.36.4",
|
|
8
|
+
"patchright": "^1.60.0",
|
|
9
|
+
"@bluefields/db": "0.2.0",
|
|
10
|
+
"@bluefields/primitives": "0.2.0",
|
|
11
|
+
"@bluefields/attest": "0.2.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "22.9.0",
|
|
15
|
+
"vitest": "2.1.4"
|
|
16
|
+
},
|
|
17
|
+
"description": "Bluefield fetcher — tiered HTML capture (HTTP → headless browser) with SSRF guard and signed provenance.",
|
|
18
|
+
"license": "AGPL-3.0-or-later",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"scraping",
|
|
37
|
+
"fetch",
|
|
38
|
+
"browser",
|
|
39
|
+
"bluefield"
|
|
40
|
+
],
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/danielvhofmann/bluefield.git",
|
|
44
|
+
"directory": "packages/fetcher"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"typecheck": "tsc --noEmit -p .",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"build": "tsc -p tsconfig.build.json"
|
|
53
|
+
}
|
|
54
|
+
}
|