@crawlbrulee/sdk 0.1.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/README.md +219 -0
- package/dist/index.cjs +538 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +781 -0
- package/dist/index.d.ts +781 -0
- package/dist/index.js +525 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @crawlbrulee/sdk
|
|
2
|
+
|
|
3
|
+
The official TypeScript / JavaScript SDK for the [crawlbrulee](https://crawlbrulee.com) web-scraping API.
|
|
4
|
+
|
|
5
|
+
- Hand-written, fully typed.
|
|
6
|
+
- ESM + CommonJS, ships its own `.d.ts`.
|
|
7
|
+
- Zero runtime dependencies — just `fetch`.
|
|
8
|
+
- Works on Node.js 20+, modern Deno, Bun, and runtimes where `fetch` is available.
|
|
9
|
+
|
|
10
|
+
> **Status:** v0.1.0 (beta). API surface is stabilizing — expect minor breaking changes between 0.x releases.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @crawlbrulee/sdk
|
|
18
|
+
# or
|
|
19
|
+
npm install @crawlbrulee/sdk
|
|
20
|
+
# or
|
|
21
|
+
yarn add @crawlbrulee/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Crawlbrulee } from '@crawlbrulee/sdk'
|
|
28
|
+
|
|
29
|
+
const crawlbrulee = new Crawlbrulee({ apiKey: 'cble_…' })
|
|
30
|
+
// or read CRAWLBRULEE_API_KEY from the environment:
|
|
31
|
+
const crawlbrulee = Crawlbrulee.fromEnv()
|
|
32
|
+
|
|
33
|
+
const page = await crawlbrulee.scrape({
|
|
34
|
+
url: 'https://example.com',
|
|
35
|
+
extract: { markdown: true, links: true },
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
console.log(page.markdown)
|
|
39
|
+
console.log(page.links?.length, 'links found')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Configuration
|
|
43
|
+
|
|
44
|
+
| Option | Default | Description |
|
|
45
|
+
| ----------- | ---------------- | ------------------------------------------------------------------------------------------ |
|
|
46
|
+
| `apiKey` | — | API key, sent as `Authorization: Bearer …`. **Required** — or use `Crawlbrulee.fromEnv()`. |
|
|
47
|
+
| `timeoutMs` | `0` (no timeout) | Per-request timeout (covers headers + body). A per-call `timeoutMs` overrides this. |
|
|
48
|
+
|
|
49
|
+
`Crawlbrulee.fromEnv(overrides?)` reads the API key from `CRAWLBRULEE_API_KEY` and forwards any other option through `overrides`.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## API reference
|
|
54
|
+
|
|
55
|
+
All methods return a `Promise` that resolves to the parsed JSON response, or rejects with a [`CrawlbruleeError`](#errors) subclass.
|
|
56
|
+
|
|
57
|
+
Every method accepts an optional second argument with per-call overrides:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
crawlbrulee.scrape(request, {
|
|
61
|
+
signal: AbortSignal, // cancel the request
|
|
62
|
+
timeoutMs: 30_000, // override the constructor timeout
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Scraping
|
|
67
|
+
|
|
68
|
+
#### `crawlbrulee.scrape(request, options?)`
|
|
69
|
+
|
|
70
|
+
Synchronously scrape a URL. The request blocks until the server is done.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const page = await crawlbrulee.scrape({
|
|
74
|
+
url: 'https://news.example.com/article-1',
|
|
75
|
+
extract: {
|
|
76
|
+
markdown: true,
|
|
77
|
+
metadata: true,
|
|
78
|
+
links: true,
|
|
79
|
+
images: true,
|
|
80
|
+
screenshot: {
|
|
81
|
+
type: 'full_page',
|
|
82
|
+
device_mode: 'desktop',
|
|
83
|
+
cleanup: { ads_and_popups: true },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
require_js: true,
|
|
87
|
+
proxy: 'advanced',
|
|
88
|
+
exclude_selectors: ['nav', 'footer'],
|
|
89
|
+
cache: { max_age: 3600 },
|
|
90
|
+
location: { country: 'US' },
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See [`ScrapeRequest`](src/types/scrape.ts) and [`ScrapeResponse`](src/types/scrape.ts) for every field, with inline documentation.
|
|
95
|
+
|
|
96
|
+
#### `crawlbrulee.scrapeAsync(request, options?)`
|
|
97
|
+
|
|
98
|
+
Submit a scrape job in the background. Returns immediately with a `job_id`.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const { job_id } = await crawlbrulee.scrapeAsync({ url: 'https://example.com' })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `crawlbrulee.getScrapeStatus(jobId, options?)`
|
|
105
|
+
|
|
106
|
+
Look up the current status of an async job — `pending`, `running`, `done`, or `failed`.
|
|
107
|
+
|
|
108
|
+
#### `crawlbrulee.getScrapeResult(jobId, options?)`
|
|
109
|
+
|
|
110
|
+
Fetch the result of a completed async job. Throws if the job hasn't finished yet.
|
|
111
|
+
|
|
112
|
+
#### `crawlbrulee.waitForScrape(jobId, options?)`
|
|
113
|
+
|
|
114
|
+
Poll an async job until it reaches a terminal state, then return the scrape result.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const { job_id } = await crawlbrulee.scrapeAsync({ url: 'https://example.com' })
|
|
118
|
+
|
|
119
|
+
const page = await crawlbrulee.waitForScrape(job_id, {
|
|
120
|
+
intervalMs: 2000, // poll every 2 s (default)
|
|
121
|
+
timeoutMs: 5 * 60 * 1000, // give up after 5 min (default; 0 to wait forever)
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Throws a `CrawlbruleeError` with `errorName: 'job_failed'` if the job ends in `failed`, or `errorName: 'request_timeout'` if the wait expires.
|
|
126
|
+
|
|
127
|
+
### Mapping
|
|
128
|
+
|
|
129
|
+
#### `crawlbrulee.map(request, options?)`
|
|
130
|
+
|
|
131
|
+
Build (or return a cached) link map for a website. Combines sitemap discovery with the freshest cached homepage scrape when available.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const result = await crawlbrulee.map({
|
|
135
|
+
url: 'https://example.com',
|
|
136
|
+
sitemap_only: false,
|
|
137
|
+
types: { internal: true, internal_subdomains: false, external: false },
|
|
138
|
+
max_urls: 5_000,
|
|
139
|
+
page: 1,
|
|
140
|
+
limit: 1_000,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
console.log(result.links.length, 'urls on page 1 of', result.meta.pagination.total_pages)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Account
|
|
147
|
+
|
|
148
|
+
#### `crawlbrulee.usage(options?)`
|
|
149
|
+
|
|
150
|
+
Return the current billing-cycle snapshot — total/used/available credits, used quota percentage, max concurrency, and the cycle reset timestamp.
|
|
151
|
+
|
|
152
|
+
#### `crawlbrulee.whoami(options?)`
|
|
153
|
+
|
|
154
|
+
Return the organization name and identifying details of the API token used to authenticate the request.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Errors
|
|
159
|
+
|
|
160
|
+
Every failure raised by the SDK extends [`CrawlbruleeError`](src/errors.ts). Typed subclasses are exported for the most actionable cases:
|
|
161
|
+
|
|
162
|
+
| Class | When it's raised |
|
|
163
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `AuthenticationError` | 401 / 403 responses (missing, invalid, or unauthorized API key). |
|
|
165
|
+
| `RateLimitError` | 429 responses. Exposes `retryAfterMs` and `limitedBy` when the server provided them. |
|
|
166
|
+
| `UsageAllocationError` | The org's plan limit was hit. Exposes `reason` (`credit_limit`, `concurrency_limit`, …) and `usage`. |
|
|
167
|
+
| `ValidationError` | 4xx caused by a bad request (`invalid_url`, `url_too_long`, `blocked_url`, …). |
|
|
168
|
+
| `NotFoundError` | 404 responses (e.g. unknown async `jobId`). |
|
|
169
|
+
| `TransportError` | Network failures, aborts, non-JSON responses, request body read failures. |
|
|
170
|
+
| `CrawlbruleeError` | Base class — used for any other API error. Always has `status`, `errorName`, `message`. |
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { Crawlbrulee, RateLimitError, UsageAllocationError } from '@crawlbrulee/sdk'
|
|
174
|
+
|
|
175
|
+
const crawlbrulee = new Crawlbrulee({ apiKey: 'cble_…' })
|
|
176
|
+
try {
|
|
177
|
+
await crawlbrulee.scrape({ url: 'https://example.com' })
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (err instanceof RateLimitError) {
|
|
180
|
+
await sleep(err.retryAfterMs ?? 1000)
|
|
181
|
+
// retry…
|
|
182
|
+
} else if (err instanceof UsageAllocationError) {
|
|
183
|
+
console.error('Plan limit hit:', err.reason, err.usage)
|
|
184
|
+
} else {
|
|
185
|
+
throw err
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
For exhaustive branching, switch on `err.errorName` — the literal-typed union is exported as `ApiErrorName`.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Cancellation and timeouts
|
|
195
|
+
|
|
196
|
+
Every method accepts an `AbortSignal`:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const controller = new AbortController()
|
|
200
|
+
const page = crawlbrulee.scrape({ url: 'https://slow.example.com' }, { signal: controller.signal })
|
|
201
|
+
|
|
202
|
+
setTimeout(() => controller.abort(), 5_000)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The per-call `timeoutMs` and the caller's signal are composed — whichever fires first wins.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Development
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pnpm install
|
|
213
|
+
pnpm test # vitest
|
|
214
|
+
pnpm typecheck # tsc --noEmit
|
|
215
|
+
pnpm lint # eslint
|
|
216
|
+
pnpm build # tsup → dist/
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The SDK has zero runtime dependencies on purpose. Please keep it that way when contributing.
|