@vakra-dev/reader-js 0.2.0 → 0.3.1
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/dist/index.cjs +42 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -14
- package/dist/index.d.ts +12 -14
- package/dist/index.js +42 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -174,14 +174,12 @@ function toReaderApiError(body, httpStatus, requestId) {
|
|
|
174
174
|
// src/client.ts
|
|
175
175
|
var DEFAULT_BASE_URL = "https://api.reader.dev";
|
|
176
176
|
var DEFAULT_TIMEOUT = 6e4;
|
|
177
|
-
var DEFAULT_MAX_RETRIES = 2;
|
|
178
177
|
var DEFAULT_POLL_INTERVAL = 2e3;
|
|
179
|
-
var DEFAULT_POLL_TIMEOUT =
|
|
178
|
+
var DEFAULT_POLL_TIMEOUT = 6e5;
|
|
180
179
|
var ReaderClient = class {
|
|
181
180
|
apiKey;
|
|
182
181
|
baseUrl;
|
|
183
182
|
timeout;
|
|
184
|
-
maxRetries;
|
|
185
183
|
extraHeaders;
|
|
186
184
|
_sessions = null;
|
|
187
185
|
constructor(config) {
|
|
@@ -191,7 +189,6 @@ var ReaderClient = class {
|
|
|
191
189
|
this.apiKey = config.apiKey;
|
|
192
190
|
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
193
191
|
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
194
|
-
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
195
192
|
this.extraHeaders = config.headers || {};
|
|
196
193
|
}
|
|
197
194
|
/**
|
|
@@ -227,7 +224,9 @@ var ReaderClient = class {
|
|
|
227
224
|
const data = envelope.data;
|
|
228
225
|
if (data && typeof data === "object" && "status" in data && "mode" in data && !("markdown" in data) && !("metadata" in data)) {
|
|
229
226
|
const jobId = String(data.id);
|
|
230
|
-
const job = await this.waitForJob(jobId
|
|
227
|
+
const job = await this.waitForJob(jobId, {
|
|
228
|
+
timeout: params.pollTimeout
|
|
229
|
+
});
|
|
231
230
|
return { kind: "job", data: job };
|
|
232
231
|
}
|
|
233
232
|
return { kind: "scrape", data };
|
|
@@ -366,67 +365,47 @@ var ReaderClient = class {
|
|
|
366
365
|
// --- Internal ---
|
|
367
366
|
async request(method, path, body) {
|
|
368
367
|
const url = path.startsWith("http") ? path : `${this.baseUrl}${path}`;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (res.status < 500 && res.status !== 429) throw err;
|
|
391
|
-
if (err instanceof RateLimitedError && err.retryAfterSeconds) {
|
|
392
|
-
await sleep(err.retryAfterSeconds * 1e3);
|
|
393
|
-
}
|
|
394
|
-
lastError = err;
|
|
395
|
-
} else {
|
|
396
|
-
const genericErr = new ReaderApiError(
|
|
397
|
-
{
|
|
398
|
-
code: "internal_error",
|
|
399
|
-
message: `Request failed with status ${res.status}`
|
|
400
|
-
},
|
|
401
|
-
res.status,
|
|
402
|
-
requestId
|
|
403
|
-
);
|
|
404
|
-
if (res.status < 500) throw genericErr;
|
|
405
|
-
lastError = genericErr;
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
return parsed;
|
|
409
|
-
}
|
|
410
|
-
} catch (err) {
|
|
411
|
-
if (err instanceof ReaderApiError) {
|
|
412
|
-
if (err.httpStatus < 500 && err.httpStatus !== 429) throw err;
|
|
413
|
-
lastError = err;
|
|
414
|
-
} else if (err instanceof Error) {
|
|
415
|
-
if (err.name === "AbortError") {
|
|
416
|
-
lastError = new ReaderApiError(
|
|
417
|
-
{ code: "scrape_timeout", message: "Request timed out" },
|
|
418
|
-
504
|
|
419
|
-
);
|
|
420
|
-
} else {
|
|
421
|
-
lastError = err;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
368
|
+
const controller = new AbortController();
|
|
369
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
370
|
+
let res;
|
|
371
|
+
try {
|
|
372
|
+
res = await fetch(url, {
|
|
373
|
+
method,
|
|
374
|
+
headers: {
|
|
375
|
+
"Content-Type": "application/json",
|
|
376
|
+
"x-api-key": this.apiKey,
|
|
377
|
+
...this.extraHeaders
|
|
378
|
+
},
|
|
379
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
380
|
+
signal: controller.signal
|
|
381
|
+
});
|
|
382
|
+
} catch (err) {
|
|
383
|
+
clearTimeout(timeoutId);
|
|
384
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
385
|
+
throw new ReaderApiError(
|
|
386
|
+
{ code: "scrape_timeout", message: "Request timed out" },
|
|
387
|
+
504
|
|
388
|
+
);
|
|
424
389
|
}
|
|
425
|
-
|
|
426
|
-
|
|
390
|
+
throw err;
|
|
391
|
+
}
|
|
392
|
+
clearTimeout(timeoutId);
|
|
393
|
+
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
394
|
+
const parsed = await res.json().catch(() => null);
|
|
395
|
+
if (!res.ok) {
|
|
396
|
+
if (parsed && "error" in parsed && parsed.error) {
|
|
397
|
+
throw toReaderApiError(parsed.error, res.status, requestId);
|
|
427
398
|
}
|
|
399
|
+
throw new ReaderApiError(
|
|
400
|
+
{
|
|
401
|
+
code: "internal_error",
|
|
402
|
+
message: `Request failed with status ${res.status}`
|
|
403
|
+
},
|
|
404
|
+
res.status,
|
|
405
|
+
requestId
|
|
406
|
+
);
|
|
428
407
|
}
|
|
429
|
-
|
|
408
|
+
return parsed;
|
|
430
409
|
}
|
|
431
410
|
};
|
|
432
411
|
var SessionsAPI = class {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { ReaderClient } from \"./client.js\";\nexport {\n ReaderApiError,\n InvalidRequestError,\n UnauthenticatedError,\n InsufficientCreditsError,\n UrlBlockedError,\n NotFoundError,\n ConflictError,\n RateLimitedError,\n ConcurrencyLimitedError,\n InternalServerError,\n UpstreamUnavailableError,\n ScrapeTimeoutError,\n toReaderApiError,\n} from \"./errors.js\";\nexport type { ReaderErrorCode, ApiErrorBody } from \"./errors.js\";\nexport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n ScrapeMetadata,\n Page,\n Job,\n JobStatus,\n JobMode,\n ProxyMode,\n Pagination,\n Credits,\n UsageEntry,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n ApiEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n SessionStatus,\n} from \"./types.js\";\n","/**\n * Typed error classes mirroring the reader-api error code catalog.\n *\n * The API returns a stable `code` field on every error response. The SDK\n * branches on that code and throws a specific subclass, so callers can\n * write:\n *\n * try {\n * await client.read({ url });\n * } catch (err) {\n * if (err instanceof InsufficientCreditsError) {\n * // err.required, err.available, err.resetAt\n * }\n * }\n *\n * There is one subclass per code in the catalog. Unknown codes fall through\n * to the base `ReaderApiError`.\n */\n\nexport type ReaderErrorCode =\n | \"invalid_request\"\n | \"unauthenticated\"\n | \"insufficient_credits\"\n | \"url_blocked\"\n | \"not_found\"\n | \"conflict\"\n | \"rate_limited\"\n | \"concurrency_limited\"\n | \"internal_error\"\n | \"upstream_unavailable\"\n | \"scrape_timeout\";\n\nexport interface ApiErrorBody {\n code: ReaderErrorCode | string;\n message: string;\n details?: Record<string, unknown>;\n docsUrl?: string;\n}\n\nexport class ReaderApiError extends Error {\n readonly code: string;\n readonly httpStatus: number;\n readonly details?: Record<string, unknown>;\n readonly docsUrl?: string;\n readonly requestId?: string;\n\n constructor(body: ApiErrorBody, httpStatus: number, requestId?: string) {\n super(body.message);\n this.name = \"ReaderApiError\";\n this.code = body.code;\n this.httpStatus = httpStatus;\n this.details = body.details;\n this.docsUrl = body.docsUrl;\n this.requestId = requestId;\n }\n}\n\nexport class InvalidRequestError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InvalidRequestError\";\n }\n}\n\nexport class UnauthenticatedError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UnauthenticatedError\";\n }\n}\n\nexport class InsufficientCreditsError extends ReaderApiError {\n readonly required?: number;\n readonly available?: number;\n readonly resetAt?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InsufficientCreditsError\";\n this.required = body.details?.required as number | undefined;\n this.available = body.details?.available as number | undefined;\n this.resetAt = body.details?.resetAt as string | undefined;\n }\n}\n\nexport class UrlBlockedError extends ReaderApiError {\n readonly url?: string;\n readonly reason?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UrlBlockedError\";\n this.url = body.details?.url as string | undefined;\n this.reason = body.details?.reason as string | undefined;\n }\n}\n\nexport class NotFoundError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ConflictError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConflictError\";\n }\n}\n\nexport class RateLimitedError extends ReaderApiError {\n readonly retryAfterSeconds?: number;\n readonly limit?: number;\n readonly windowSeconds?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"RateLimitedError\";\n this.retryAfterSeconds = body.details?.retryAfterSeconds as number | undefined;\n this.limit = body.details?.limit as number | undefined;\n this.windowSeconds = body.details?.windowSeconds as number | undefined;\n }\n}\n\nexport class ConcurrencyLimitedError extends ReaderApiError {\n readonly active?: number;\n readonly max?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConcurrencyLimitedError\";\n this.active = body.details?.active as number | undefined;\n this.max = body.details?.max as number | undefined;\n }\n}\n\nexport class InternalServerError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InternalServerError\";\n }\n}\n\nexport class UpstreamUnavailableError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UpstreamUnavailableError\";\n }\n}\n\nexport class ScrapeTimeoutError extends ReaderApiError {\n readonly timeoutMs?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ScrapeTimeoutError\";\n this.timeoutMs = body.details?.timeoutMs as number | undefined;\n }\n}\n\n/**\n * Construct the right error subclass from an error response body.\n * Unknown codes fall through to the base class.\n */\nexport function toReaderApiError(\n body: ApiErrorBody,\n httpStatus: number,\n requestId?: string,\n): ReaderApiError {\n switch (body.code) {\n case \"invalid_request\":\n return new InvalidRequestError(body, httpStatus, requestId);\n case \"unauthenticated\":\n return new UnauthenticatedError(body, httpStatus, requestId);\n case \"insufficient_credits\":\n return new InsufficientCreditsError(body, httpStatus, requestId);\n case \"url_blocked\":\n return new UrlBlockedError(body, httpStatus, requestId);\n case \"not_found\":\n return new NotFoundError(body, httpStatus, requestId);\n case \"conflict\":\n return new ConflictError(body, httpStatus, requestId);\n case \"rate_limited\":\n return new RateLimitedError(body, httpStatus, requestId);\n case \"concurrency_limited\":\n return new ConcurrencyLimitedError(body, httpStatus, requestId);\n case \"internal_error\":\n return new InternalServerError(body, httpStatus, requestId);\n case \"upstream_unavailable\":\n return new UpstreamUnavailableError(body, httpStatus, requestId);\n case \"scrape_timeout\":\n return new ScrapeTimeoutError(body, httpStatus, requestId);\n default:\n return new ReaderApiError(body, httpStatus, requestId);\n }\n}\n","/**\n * Reader SDK Client\n *\n * @example\n * import { ReaderClient } from \"@vakra-dev/reader-js\";\n *\n * const client = new ReaderClient({ apiKey: \"rdr_your_key\" });\n *\n * // Synchronous scrape (single URL)\n * const result = await client.read({ url: \"https://example.com\" });\n * if (result.kind === \"scrape\") {\n * console.log(result.data.markdown);\n * }\n *\n * // Batch (returns a completed Job with all results collected)\n * const batch = await client.read({ urls: [\"url1\", \"url2\"] });\n * if (batch.kind === \"job\") {\n * for (const page of batch.data.results) {\n * console.log(page.url, page.markdown?.length);\n * }\n * }\n */\n\nimport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n Job,\n Credits,\n Page,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n} from \"./types.js\";\nimport {\n toReaderApiError,\n ReaderApiError,\n ScrapeTimeoutError,\n RateLimitedError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.reader.dev\";\nconst DEFAULT_TIMEOUT = 60_000;\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_POLL_INTERVAL = 2_000;\nconst DEFAULT_POLL_TIMEOUT = 300_000; // 5 minutes\n\ninterface JobWithPagination {\n data: Job;\n pagination: { total: number; skip: number; limit: number; hasMore: boolean; next?: string };\n}\n\nexport class ReaderClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n private maxRetries: number;\n private extraHeaders: Record<string, string>;\n private _sessions: SessionsAPI | null = null;\n\n constructor(config: ReaderClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.extraHeaders = config.headers || {};\n }\n\n /**\n * Browser sessions API.\n *\n * @example\n * ```typescript\n * const session = await client.sessions.create();\n * const browser = await chromium.connectOverCDP(session.wsEndpoint);\n * // ... use Playwright ...\n * await client.sessions.stop(session.sessionId);\n * ```\n */\n get sessions(): SessionsAPI {\n if (!this._sessions) {\n this._sessions = new SessionsAPI(this.request.bind(this));\n }\n return this._sessions;\n }\n\n /**\n * Read (scrape, batch, or crawl) one or more URLs.\n *\n * - Single URL → sync scrape, returns immediately with `{ kind: \"scrape\", data }`\n * - Multiple URLs or URL + maxDepth/maxPages → async job; this method polls\n * until the job terminates and returns `{ kind: \"job\", data }`.\n */\n async read(params: ReadParams): Promise<ReadResult> {\n const envelope = await this.request<SuccessEnvelope<unknown>>(\n \"POST\",\n \"/v1/read\",\n params,\n );\n\n const data = envelope.data as Record<string, unknown>;\n\n // Async job response: data.id + data.status present, no markdown/html/metadata\n if (\n data &&\n typeof data === \"object\" &&\n \"status\" in data &&\n \"mode\" in data &&\n !(\"markdown\" in data) &&\n !(\"metadata\" in data)\n ) {\n const jobId = String((data as { id: unknown }).id);\n const job = await this.waitForJob(jobId);\n return { kind: \"job\", data: job };\n }\n\n // Synchronous scrape: data has markdown/html/metadata\n return { kind: \"scrape\", data: data as unknown as ScrapeResult };\n }\n\n /**\n * Get job status and a single page of results.\n */\n async getJob(\n jobId: string,\n opts?: { skip?: number; limit?: number },\n ): Promise<{ job: Job; hasMore: boolean; next?: string }> {\n const query = new URLSearchParams();\n if (opts?.skip !== undefined) query.set(\"skip\", String(opts.skip));\n if (opts?.limit !== undefined) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n\n const envelope = await this.request<JobWithPagination>(\n \"GET\",\n `/v1/jobs/${jobId}${qs ? `?${qs}` : \"\"}`,\n );\n\n return {\n job: envelope.data,\n hasMore: envelope.pagination.hasMore,\n next: envelope.pagination.next,\n };\n }\n\n /**\n * Fetch all job result pages by following pagination.\n */\n async getAllJobResults(jobId: string): Promise<Page[]> {\n const pages: Page[] = [];\n let skip = 0;\n const limit = 100;\n\n while (true) {\n const { job, hasMore } = await this.getJob(jobId, { skip, limit });\n pages.push(...(job.results ?? []));\n if (!hasMore) break;\n skip += limit;\n }\n\n return pages;\n }\n\n /**\n * Cancel a job. Throws `ConflictError` if the job is already terminal.\n */\n async cancelJob(jobId: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/jobs/${jobId}`);\n }\n\n /**\n * Retry the failed URLs in a job. Throws `InvalidRequestError` if no\n * failed URLs exist.\n */\n async retryJob(jobId: string): Promise<{ id: string; status: string; retrying: number }> {\n const envelope = await this.request<\n SuccessEnvelope<{ id: string; status: string; retrying: number }>\n >(\"POST\", `/v1/jobs/${jobId}/retry`);\n return envelope.data;\n }\n\n /**\n * Poll a job until it completes, fails, or is cancelled. Collects all\n * paginated results when complete.\n */\n async waitForJob(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): Promise<Job> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { limit: 1 });\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n if (job.status === \"completed\") {\n job.results = await this.getAllJobResults(jobId);\n }\n return job;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} polling timed out after ${timeout}ms`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Stream job results as they arrive via polling.\n *\n * @example\n * for await (const event of client.stream(jobId)) {\n * if (event.type === \"page\") console.log(event.data.url);\n * if (event.type === \"done\") break;\n * }\n */\n async *stream(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): AsyncGenerator<StreamEvent> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n let lastCompleted = 0;\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { skip: lastCompleted, limit: 100 });\n\n yield {\n type: \"progress\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n\n for (const page of job.results ?? []) {\n if (page.error) {\n yield { type: \"error\", url: page.url, error: page.error };\n } else {\n yield { type: \"page\", data: page };\n }\n lastCompleted += 1;\n }\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n yield {\n type: \"done\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n return;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} stream timed out`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Get the current credit balance for this workspace.\n */\n async getCredits(): Promise<Credits> {\n const envelope = await this.request<SuccessEnvelope<Credits>>(\"GET\", \"/v1/usage/credits\");\n return envelope.data;\n }\n\n // --- Internal ---\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = path.startsWith(\"http\") ? path : `${this.baseUrl}${path}`;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const res = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n ...this.extraHeaders,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const parsed = (await res.json().catch(() => null)) as\n | SuccessEnvelope<unknown>\n | PaginatedEnvelope<unknown>\n | ErrorEnvelope\n | null;\n\n if (!res.ok) {\n if (parsed && \"error\" in parsed && parsed.error) {\n const err = toReaderApiError(parsed.error, res.status, requestId);\n\n // Don't retry client errors except 429\n if (res.status < 500 && res.status !== 429) throw err;\n\n // Honor Retry-After from the rate-limited response\n if (err instanceof RateLimitedError && err.retryAfterSeconds) {\n await sleep(err.retryAfterSeconds * 1000);\n }\n\n lastError = err;\n } else {\n const genericErr = new ReaderApiError(\n {\n code: \"internal_error\",\n message: `Request failed with status ${res.status}`,\n },\n res.status,\n requestId,\n );\n if (res.status < 500) throw genericErr;\n lastError = genericErr;\n }\n } else {\n return parsed as unknown as T;\n }\n } catch (err) {\n if (err instanceof ReaderApiError) {\n if (err.httpStatus < 500 && err.httpStatus !== 429) throw err;\n lastError = err;\n } else if (err instanceof Error) {\n if (err.name === \"AbortError\") {\n lastError = new ReaderApiError(\n { code: \"scrape_timeout\", message: \"Request timed out\" },\n 504,\n );\n } else {\n lastError = err;\n }\n }\n }\n\n // Exponential backoff before retry\n if (attempt < this.maxRetries) {\n await sleep(Math.pow(2, attempt) * 1000);\n }\n }\n\n throw (\n lastError ??\n new ReaderApiError({ code: \"internal_error\", message: \"Request failed\" }, 500)\n );\n }\n}\n\n// ─── Sessions API ────────────────────────────────────────────────────\n\ntype RequestFn = <T>(method: string, path: string, body?: unknown) => Promise<T>;\n\nclass SessionsAPI {\n constructor(private request: RequestFn) {}\n\n /**\n * Create a browser session. Returns a CDP WebSocket URL for\n * Playwright/Puppeteer connection.\n */\n async create(params?: CreateSessionParams): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"POST\",\n \"/v1/sessions\",\n params ?? {},\n );\n return envelope.data;\n }\n\n /**\n * Get session status.\n */\n async get(sessionId: string): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"GET\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * Stop a browser session.\n */\n async stop(sessionId: string): Promise<StopSessionResult> {\n const envelope = await this.request<SuccessEnvelope<StopSessionResult>>(\n \"DELETE\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * List active sessions.\n */\n async list(): Promise<SessionInfo[]> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo[]>>(\n \"GET\",\n \"/v1/sessions\",\n );\n return envelope.data;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { ReaderClient } from \"./client.js\";\nexport {\n ReaderApiError,\n InvalidRequestError,\n UnauthenticatedError,\n InsufficientCreditsError,\n UrlBlockedError,\n NotFoundError,\n ConflictError,\n RateLimitedError,\n ConcurrencyLimitedError,\n InternalServerError,\n UpstreamUnavailableError,\n ScrapeTimeoutError,\n toReaderApiError,\n} from \"./errors.js\";\nexport type { ReaderErrorCode, ApiErrorBody } from \"./errors.js\";\nexport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n ScrapeMetadata,\n Page,\n Job,\n JobStatus,\n JobMode,\n ProxyMode,\n Pagination,\n Credits,\n UsageEntry,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n ApiEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n SessionStatus,\n} from \"./types.js\";\n","/**\n * Typed error classes mirroring the reader-api error code catalog.\n *\n * The API returns a stable `code` field on every error response. The SDK\n * branches on that code and throws a specific subclass, so callers can\n * write:\n *\n * try {\n * await client.read({ url });\n * } catch (err) {\n * if (err instanceof InsufficientCreditsError) {\n * // err.required, err.available, err.resetAt\n * }\n * }\n *\n * There is one subclass per code in the catalog. Unknown codes fall through\n * to the base `ReaderApiError`.\n */\n\nexport type ReaderErrorCode =\n | \"invalid_request\"\n | \"unauthenticated\"\n | \"insufficient_credits\"\n | \"url_blocked\"\n | \"not_found\"\n | \"conflict\"\n | \"rate_limited\"\n | \"concurrency_limited\"\n | \"internal_error\"\n | \"upstream_unavailable\"\n | \"scrape_timeout\";\n\nexport interface ApiErrorBody {\n code: ReaderErrorCode | string;\n message: string;\n details?: Record<string, unknown>;\n docsUrl?: string;\n}\n\nexport class ReaderApiError extends Error {\n readonly code: string;\n readonly httpStatus: number;\n readonly details?: Record<string, unknown>;\n readonly docsUrl?: string;\n readonly requestId?: string;\n\n constructor(body: ApiErrorBody, httpStatus: number, requestId?: string) {\n super(body.message);\n this.name = \"ReaderApiError\";\n this.code = body.code;\n this.httpStatus = httpStatus;\n this.details = body.details;\n this.docsUrl = body.docsUrl;\n this.requestId = requestId;\n }\n}\n\nexport class InvalidRequestError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InvalidRequestError\";\n }\n}\n\nexport class UnauthenticatedError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UnauthenticatedError\";\n }\n}\n\nexport class InsufficientCreditsError extends ReaderApiError {\n readonly required?: number;\n readonly available?: number;\n readonly resetAt?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InsufficientCreditsError\";\n this.required = body.details?.required as number | undefined;\n this.available = body.details?.available as number | undefined;\n this.resetAt = body.details?.resetAt as string | undefined;\n }\n}\n\nexport class UrlBlockedError extends ReaderApiError {\n readonly url?: string;\n readonly reason?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UrlBlockedError\";\n this.url = body.details?.url as string | undefined;\n this.reason = body.details?.reason as string | undefined;\n }\n}\n\nexport class NotFoundError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ConflictError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConflictError\";\n }\n}\n\nexport class RateLimitedError extends ReaderApiError {\n readonly retryAfterSeconds?: number;\n readonly limit?: number;\n readonly windowSeconds?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"RateLimitedError\";\n this.retryAfterSeconds = body.details?.retryAfterSeconds as number | undefined;\n this.limit = body.details?.limit as number | undefined;\n this.windowSeconds = body.details?.windowSeconds as number | undefined;\n }\n}\n\nexport class ConcurrencyLimitedError extends ReaderApiError {\n readonly active?: number;\n readonly max?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConcurrencyLimitedError\";\n this.active = body.details?.active as number | undefined;\n this.max = body.details?.max as number | undefined;\n }\n}\n\nexport class InternalServerError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InternalServerError\";\n }\n}\n\nexport class UpstreamUnavailableError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UpstreamUnavailableError\";\n }\n}\n\nexport class ScrapeTimeoutError extends ReaderApiError {\n readonly timeoutMs?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ScrapeTimeoutError\";\n this.timeoutMs = body.details?.timeoutMs as number | undefined;\n }\n}\n\n/**\n * Construct the right error subclass from an error response body.\n * Unknown codes fall through to the base class.\n */\nexport function toReaderApiError(\n body: ApiErrorBody,\n httpStatus: number,\n requestId?: string,\n): ReaderApiError {\n switch (body.code) {\n case \"invalid_request\":\n return new InvalidRequestError(body, httpStatus, requestId);\n case \"unauthenticated\":\n return new UnauthenticatedError(body, httpStatus, requestId);\n case \"insufficient_credits\":\n return new InsufficientCreditsError(body, httpStatus, requestId);\n case \"url_blocked\":\n return new UrlBlockedError(body, httpStatus, requestId);\n case \"not_found\":\n return new NotFoundError(body, httpStatus, requestId);\n case \"conflict\":\n return new ConflictError(body, httpStatus, requestId);\n case \"rate_limited\":\n return new RateLimitedError(body, httpStatus, requestId);\n case \"concurrency_limited\":\n return new ConcurrencyLimitedError(body, httpStatus, requestId);\n case \"internal_error\":\n return new InternalServerError(body, httpStatus, requestId);\n case \"upstream_unavailable\":\n return new UpstreamUnavailableError(body, httpStatus, requestId);\n case \"scrape_timeout\":\n return new ScrapeTimeoutError(body, httpStatus, requestId);\n default:\n return new ReaderApiError(body, httpStatus, requestId);\n }\n}\n","/**\n * Reader SDK Client\n *\n * @example\n * import { ReaderClient } from \"@vakra-dev/reader-js\";\n *\n * const client = new ReaderClient({ apiKey: \"rdr_your_key\" });\n *\n * // Synchronous scrape (single URL)\n * const result = await client.read({ url: \"https://example.com\" });\n * if (result.kind === \"scrape\") {\n * console.log(result.data.markdown);\n * }\n *\n * // Batch (returns a completed Job with all results collected)\n * const batch = await client.read({ urls: [\"url1\", \"url2\"] });\n * if (batch.kind === \"job\") {\n * for (const page of batch.data.results) {\n * console.log(page.url, page.markdown?.length);\n * }\n * }\n */\n\nimport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n Job,\n Credits,\n Page,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n} from \"./types.js\";\nimport {\n toReaderApiError,\n ReaderApiError,\n ScrapeTimeoutError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.reader.dev\";\nconst DEFAULT_TIMEOUT = 60_000;\nconst DEFAULT_POLL_INTERVAL = 2_000;\nconst DEFAULT_POLL_TIMEOUT = 600_000; // 10 minutes\n\ninterface JobWithPagination {\n data: Job;\n pagination: { total: number; skip: number; limit: number; hasMore: boolean; next?: string };\n}\n\nexport class ReaderClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n private extraHeaders: Record<string, string>;\n private _sessions: SessionsAPI | null = null;\n\n constructor(config: ReaderClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.extraHeaders = config.headers || {};\n }\n\n /**\n * Browser sessions API.\n *\n * @example\n * ```typescript\n * const session = await client.sessions.create();\n * const browser = await chromium.connectOverCDP(session.wsEndpoint);\n * // ... use Playwright ...\n * await client.sessions.stop(session.sessionId);\n * ```\n */\n get sessions(): SessionsAPI {\n if (!this._sessions) {\n this._sessions = new SessionsAPI(this.request.bind(this));\n }\n return this._sessions;\n }\n\n /**\n * Read (scrape, batch, or crawl) one or more URLs.\n *\n * - Single URL → sync scrape, returns immediately with `{ kind: \"scrape\", data }`\n * - Multiple URLs or URL + maxDepth/maxPages → async job; this method polls\n * until the job terminates and returns `{ kind: \"job\", data }`.\n */\n async read(params: ReadParams): Promise<ReadResult> {\n const envelope = await this.request<SuccessEnvelope<unknown>>(\n \"POST\",\n \"/v1/read\",\n params,\n );\n\n const data = envelope.data as Record<string, unknown>;\n\n // Async job response: data.id + data.status present, no markdown/html/metadata\n if (\n data &&\n typeof data === \"object\" &&\n \"status\" in data &&\n \"mode\" in data &&\n !(\"markdown\" in data) &&\n !(\"metadata\" in data)\n ) {\n const jobId = String((data as { id: unknown }).id);\n const job = await this.waitForJob(jobId, {\n timeout: params.pollTimeout,\n });\n return { kind: \"job\", data: job };\n }\n\n // Synchronous scrape: data has markdown/html/metadata\n return { kind: \"scrape\", data: data as unknown as ScrapeResult };\n }\n\n /**\n * Get job status and a single page of results.\n */\n async getJob(\n jobId: string,\n opts?: { skip?: number; limit?: number },\n ): Promise<{ job: Job; hasMore: boolean; next?: string }> {\n const query = new URLSearchParams();\n if (opts?.skip !== undefined) query.set(\"skip\", String(opts.skip));\n if (opts?.limit !== undefined) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n\n const envelope = await this.request<JobWithPagination>(\n \"GET\",\n `/v1/jobs/${jobId}${qs ? `?${qs}` : \"\"}`,\n );\n\n return {\n job: envelope.data,\n hasMore: envelope.pagination.hasMore,\n next: envelope.pagination.next,\n };\n }\n\n /**\n * Fetch all job result pages by following pagination.\n */\n async getAllJobResults(jobId: string): Promise<Page[]> {\n const pages: Page[] = [];\n let skip = 0;\n const limit = 100;\n\n while (true) {\n const { job, hasMore } = await this.getJob(jobId, { skip, limit });\n pages.push(...(job.results ?? []));\n if (!hasMore) break;\n skip += limit;\n }\n\n return pages;\n }\n\n /**\n * Cancel a job. Throws `ConflictError` if the job is already terminal.\n */\n async cancelJob(jobId: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/jobs/${jobId}`);\n }\n\n /**\n * Retry the failed URLs in a job. Throws `InvalidRequestError` if no\n * failed URLs exist.\n */\n async retryJob(jobId: string): Promise<{ id: string; status: string; retrying: number }> {\n const envelope = await this.request<\n SuccessEnvelope<{ id: string; status: string; retrying: number }>\n >(\"POST\", `/v1/jobs/${jobId}/retry`);\n return envelope.data;\n }\n\n /**\n * Poll a job until it completes, fails, or is cancelled. Collects all\n * paginated results when complete.\n */\n async waitForJob(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): Promise<Job> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { limit: 1 });\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n if (job.status === \"completed\") {\n job.results = await this.getAllJobResults(jobId);\n }\n return job;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} polling timed out after ${timeout}ms`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Stream job results as they arrive via polling.\n *\n * @example\n * for await (const event of client.stream(jobId)) {\n * if (event.type === \"page\") console.log(event.data.url);\n * if (event.type === \"done\") break;\n * }\n */\n async *stream(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): AsyncGenerator<StreamEvent> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n let lastCompleted = 0;\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { skip: lastCompleted, limit: 100 });\n\n yield {\n type: \"progress\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n\n for (const page of job.results ?? []) {\n if (page.error) {\n yield { type: \"error\", url: page.url, error: page.error };\n } else {\n yield { type: \"page\", data: page };\n }\n lastCompleted += 1;\n }\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n yield {\n type: \"done\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n return;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} stream timed out`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Get the current credit balance for this workspace.\n */\n async getCredits(): Promise<Credits> {\n const envelope = await this.request<SuccessEnvelope<Credits>>(\"GET\", \"/v1/usage/credits\");\n return envelope.data;\n }\n\n // --- Internal ---\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = path.startsWith(\"http\") ? path : `${this.baseUrl}${path}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n ...this.extraHeaders,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new ReaderApiError(\n { code: \"scrape_timeout\", message: \"Request timed out\" },\n 504,\n );\n }\n throw err;\n }\n\n clearTimeout(timeoutId);\n\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const parsed = (await res.json().catch(() => null)) as\n | SuccessEnvelope<unknown>\n | PaginatedEnvelope<unknown>\n | ErrorEnvelope\n | null;\n\n if (!res.ok) {\n if (parsed && \"error\" in parsed && parsed.error) {\n throw toReaderApiError(parsed.error, res.status, requestId);\n }\n throw new ReaderApiError(\n {\n code: \"internal_error\",\n message: `Request failed with status ${res.status}`,\n },\n res.status,\n requestId,\n );\n }\n\n return parsed as unknown as T;\n }\n}\n\n// ─── Sessions API ────────────────────────────────────────────────────\n\ntype RequestFn = <T>(method: string, path: string, body?: unknown) => Promise<T>;\n\nclass SessionsAPI {\n constructor(private request: RequestFn) {}\n\n /**\n * Create a browser session. Returns a CDP WebSocket URL for\n * Playwright/Puppeteer connection.\n */\n async create(params?: CreateSessionParams): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"POST\",\n \"/v1/sessions\",\n params ?? {},\n );\n return envelope.data;\n }\n\n /**\n * Get session status.\n */\n async get(sessionId: string): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"GET\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * Stop a browser session.\n */\n async stop(sessionId: string): Promise<StopSessionResult> {\n const envelope = await this.request<SuccessEnvelope<StopSessionResult>>(\n \"DELETE\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * List active sessions.\n */\n async list(): Promise<SessionInfo[]> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo[]>>(\n \"GET\",\n \"/v1/sessions\",\n );\n return envelope.data;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -8,20 +8,18 @@ interface ReaderClientConfig {
|
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
/** Request timeout in ms (default: 60000) */
|
|
10
10
|
timeout?: number;
|
|
11
|
-
/** Max retries on transient failures (default: 2) */
|
|
12
|
-
maxRetries?: number;
|
|
13
11
|
/** Extra headers to include in every request (e.g. x-request-id for tracing) */
|
|
14
12
|
headers?: Record<string, string>;
|
|
15
13
|
}
|
|
16
|
-
/** Public proxy mode. `
|
|
17
|
-
type ProxyMode = "standard" | "
|
|
14
|
+
/** Public proxy mode. `standard` is fast and affordable; `premium` uses stronger proxies for anti-bot sites. */
|
|
15
|
+
type ProxyMode = "standard" | "premium";
|
|
18
16
|
interface ReadParams {
|
|
19
17
|
/** Single URL to scrape */
|
|
20
18
|
url?: string;
|
|
21
19
|
/** Multiple URLs for batch scraping */
|
|
22
20
|
urls?: string[];
|
|
23
21
|
/** Output formats (default: ["markdown"]) */
|
|
24
|
-
formats?: Array<"markdown" | "html">;
|
|
22
|
+
formats?: Array<"markdown" | "html" | "screenshot">;
|
|
25
23
|
/** Extract main content only (default: true) */
|
|
26
24
|
onlyMainContent?: boolean;
|
|
27
25
|
/** CSS selectors to include */
|
|
@@ -32,7 +30,7 @@ interface ReadParams {
|
|
|
32
30
|
waitForSelector?: string;
|
|
33
31
|
/** Per-URL timeout in ms (default: 30000) */
|
|
34
32
|
timeoutMs?: number;
|
|
35
|
-
/** Proxy mode: standard
|
|
33
|
+
/** Proxy mode: standard (default) or premium */
|
|
36
34
|
proxyMode?: ProxyMode;
|
|
37
35
|
/** Max crawl depth (triggers crawl mode) */
|
|
38
36
|
maxDepth?: number;
|
|
@@ -48,6 +46,8 @@ interface ReadParams {
|
|
|
48
46
|
};
|
|
49
47
|
/** Batch concurrency override */
|
|
50
48
|
batchConcurrency?: number;
|
|
49
|
+
/** Polling timeout for async jobs in ms (default: 600000) */
|
|
50
|
+
pollTimeout?: number;
|
|
51
51
|
}
|
|
52
52
|
interface ScrapeMetadata {
|
|
53
53
|
title?: string | null;
|
|
@@ -55,19 +55,17 @@ interface ScrapeMetadata {
|
|
|
55
55
|
statusCode?: number;
|
|
56
56
|
duration: number;
|
|
57
57
|
cached: boolean;
|
|
58
|
-
/** Resolved proxy mode — `"standard"` or `"
|
|
59
|
-
proxyMode?: "standard" | "
|
|
60
|
-
/** True if `auto` escalated from standard to stealth for this page. */
|
|
61
|
-
proxyEscalated?: boolean;
|
|
58
|
+
/** Resolved proxy mode — `"standard"` or `"premium"`. Omitted on cache hits. */
|
|
59
|
+
proxyMode?: "standard" | "premium";
|
|
62
60
|
scrapedAt: string;
|
|
63
61
|
}
|
|
64
62
|
interface Page {
|
|
65
63
|
url: string;
|
|
66
64
|
markdown?: string;
|
|
67
65
|
html?: string;
|
|
66
|
+
screenshot?: string;
|
|
68
67
|
statusCode?: number;
|
|
69
|
-
proxyMode?: "standard" | "
|
|
70
|
-
proxyEscalated?: boolean;
|
|
68
|
+
proxyMode?: "standard" | "premium";
|
|
71
69
|
credits?: number;
|
|
72
70
|
metadata?: ScrapeMetadata | Record<string, unknown>;
|
|
73
71
|
error?: string;
|
|
@@ -79,6 +77,7 @@ interface ScrapeResult {
|
|
|
79
77
|
finalUrl?: string;
|
|
80
78
|
markdown?: string;
|
|
81
79
|
html?: string;
|
|
80
|
+
screenshot?: string;
|
|
82
81
|
metadata: ScrapeMetadata;
|
|
83
82
|
}
|
|
84
83
|
type JobStatus = "queued" | "processing" | "completed" | "failed" | "cancelled";
|
|
@@ -126,7 +125,7 @@ interface UsageEntry {
|
|
|
126
125
|
duration: number;
|
|
127
126
|
status: "success" | "error";
|
|
128
127
|
cached: boolean;
|
|
129
|
-
proxyMode: "standard" | "
|
|
128
|
+
proxyMode: "standard" | "premium" | null;
|
|
130
129
|
credits: number;
|
|
131
130
|
error: string | null;
|
|
132
131
|
createdAt: string;
|
|
@@ -215,7 +214,6 @@ declare class ReaderClient {
|
|
|
215
214
|
private apiKey;
|
|
216
215
|
private baseUrl;
|
|
217
216
|
private timeout;
|
|
218
|
-
private maxRetries;
|
|
219
217
|
private extraHeaders;
|
|
220
218
|
private _sessions;
|
|
221
219
|
constructor(config: ReaderClientConfig);
|
package/dist/index.d.ts
CHANGED
|
@@ -8,20 +8,18 @@ interface ReaderClientConfig {
|
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
/** Request timeout in ms (default: 60000) */
|
|
10
10
|
timeout?: number;
|
|
11
|
-
/** Max retries on transient failures (default: 2) */
|
|
12
|
-
maxRetries?: number;
|
|
13
11
|
/** Extra headers to include in every request (e.g. x-request-id for tracing) */
|
|
14
12
|
headers?: Record<string, string>;
|
|
15
13
|
}
|
|
16
|
-
/** Public proxy mode. `
|
|
17
|
-
type ProxyMode = "standard" | "
|
|
14
|
+
/** Public proxy mode. `standard` is fast and affordable; `premium` uses stronger proxies for anti-bot sites. */
|
|
15
|
+
type ProxyMode = "standard" | "premium";
|
|
18
16
|
interface ReadParams {
|
|
19
17
|
/** Single URL to scrape */
|
|
20
18
|
url?: string;
|
|
21
19
|
/** Multiple URLs for batch scraping */
|
|
22
20
|
urls?: string[];
|
|
23
21
|
/** Output formats (default: ["markdown"]) */
|
|
24
|
-
formats?: Array<"markdown" | "html">;
|
|
22
|
+
formats?: Array<"markdown" | "html" | "screenshot">;
|
|
25
23
|
/** Extract main content only (default: true) */
|
|
26
24
|
onlyMainContent?: boolean;
|
|
27
25
|
/** CSS selectors to include */
|
|
@@ -32,7 +30,7 @@ interface ReadParams {
|
|
|
32
30
|
waitForSelector?: string;
|
|
33
31
|
/** Per-URL timeout in ms (default: 30000) */
|
|
34
32
|
timeoutMs?: number;
|
|
35
|
-
/** Proxy mode: standard
|
|
33
|
+
/** Proxy mode: standard (default) or premium */
|
|
36
34
|
proxyMode?: ProxyMode;
|
|
37
35
|
/** Max crawl depth (triggers crawl mode) */
|
|
38
36
|
maxDepth?: number;
|
|
@@ -48,6 +46,8 @@ interface ReadParams {
|
|
|
48
46
|
};
|
|
49
47
|
/** Batch concurrency override */
|
|
50
48
|
batchConcurrency?: number;
|
|
49
|
+
/** Polling timeout for async jobs in ms (default: 600000) */
|
|
50
|
+
pollTimeout?: number;
|
|
51
51
|
}
|
|
52
52
|
interface ScrapeMetadata {
|
|
53
53
|
title?: string | null;
|
|
@@ -55,19 +55,17 @@ interface ScrapeMetadata {
|
|
|
55
55
|
statusCode?: number;
|
|
56
56
|
duration: number;
|
|
57
57
|
cached: boolean;
|
|
58
|
-
/** Resolved proxy mode — `"standard"` or `"
|
|
59
|
-
proxyMode?: "standard" | "
|
|
60
|
-
/** True if `auto` escalated from standard to stealth for this page. */
|
|
61
|
-
proxyEscalated?: boolean;
|
|
58
|
+
/** Resolved proxy mode — `"standard"` or `"premium"`. Omitted on cache hits. */
|
|
59
|
+
proxyMode?: "standard" | "premium";
|
|
62
60
|
scrapedAt: string;
|
|
63
61
|
}
|
|
64
62
|
interface Page {
|
|
65
63
|
url: string;
|
|
66
64
|
markdown?: string;
|
|
67
65
|
html?: string;
|
|
66
|
+
screenshot?: string;
|
|
68
67
|
statusCode?: number;
|
|
69
|
-
proxyMode?: "standard" | "
|
|
70
|
-
proxyEscalated?: boolean;
|
|
68
|
+
proxyMode?: "standard" | "premium";
|
|
71
69
|
credits?: number;
|
|
72
70
|
metadata?: ScrapeMetadata | Record<string, unknown>;
|
|
73
71
|
error?: string;
|
|
@@ -79,6 +77,7 @@ interface ScrapeResult {
|
|
|
79
77
|
finalUrl?: string;
|
|
80
78
|
markdown?: string;
|
|
81
79
|
html?: string;
|
|
80
|
+
screenshot?: string;
|
|
82
81
|
metadata: ScrapeMetadata;
|
|
83
82
|
}
|
|
84
83
|
type JobStatus = "queued" | "processing" | "completed" | "failed" | "cancelled";
|
|
@@ -126,7 +125,7 @@ interface UsageEntry {
|
|
|
126
125
|
duration: number;
|
|
127
126
|
status: "success" | "error";
|
|
128
127
|
cached: boolean;
|
|
129
|
-
proxyMode: "standard" | "
|
|
128
|
+
proxyMode: "standard" | "premium" | null;
|
|
130
129
|
credits: number;
|
|
131
130
|
error: string | null;
|
|
132
131
|
createdAt: string;
|
|
@@ -215,7 +214,6 @@ declare class ReaderClient {
|
|
|
215
214
|
private apiKey;
|
|
216
215
|
private baseUrl;
|
|
217
216
|
private timeout;
|
|
218
|
-
private maxRetries;
|
|
219
217
|
private extraHeaders;
|
|
220
218
|
private _sessions;
|
|
221
219
|
constructor(config: ReaderClientConfig);
|
package/dist/index.js
CHANGED
|
@@ -135,14 +135,12 @@ function toReaderApiError(body, httpStatus, requestId) {
|
|
|
135
135
|
// src/client.ts
|
|
136
136
|
var DEFAULT_BASE_URL = "https://api.reader.dev";
|
|
137
137
|
var DEFAULT_TIMEOUT = 6e4;
|
|
138
|
-
var DEFAULT_MAX_RETRIES = 2;
|
|
139
138
|
var DEFAULT_POLL_INTERVAL = 2e3;
|
|
140
|
-
var DEFAULT_POLL_TIMEOUT =
|
|
139
|
+
var DEFAULT_POLL_TIMEOUT = 6e5;
|
|
141
140
|
var ReaderClient = class {
|
|
142
141
|
apiKey;
|
|
143
142
|
baseUrl;
|
|
144
143
|
timeout;
|
|
145
|
-
maxRetries;
|
|
146
144
|
extraHeaders;
|
|
147
145
|
_sessions = null;
|
|
148
146
|
constructor(config) {
|
|
@@ -152,7 +150,6 @@ var ReaderClient = class {
|
|
|
152
150
|
this.apiKey = config.apiKey;
|
|
153
151
|
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
154
152
|
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
155
|
-
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
156
153
|
this.extraHeaders = config.headers || {};
|
|
157
154
|
}
|
|
158
155
|
/**
|
|
@@ -188,7 +185,9 @@ var ReaderClient = class {
|
|
|
188
185
|
const data = envelope.data;
|
|
189
186
|
if (data && typeof data === "object" && "status" in data && "mode" in data && !("markdown" in data) && !("metadata" in data)) {
|
|
190
187
|
const jobId = String(data.id);
|
|
191
|
-
const job = await this.waitForJob(jobId
|
|
188
|
+
const job = await this.waitForJob(jobId, {
|
|
189
|
+
timeout: params.pollTimeout
|
|
190
|
+
});
|
|
192
191
|
return { kind: "job", data: job };
|
|
193
192
|
}
|
|
194
193
|
return { kind: "scrape", data };
|
|
@@ -327,67 +326,47 @@ var ReaderClient = class {
|
|
|
327
326
|
// --- Internal ---
|
|
328
327
|
async request(method, path, body) {
|
|
329
328
|
const url = path.startsWith("http") ? path : `${this.baseUrl}${path}`;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (res.status < 500 && res.status !== 429) throw err;
|
|
352
|
-
if (err instanceof RateLimitedError && err.retryAfterSeconds) {
|
|
353
|
-
await sleep(err.retryAfterSeconds * 1e3);
|
|
354
|
-
}
|
|
355
|
-
lastError = err;
|
|
356
|
-
} else {
|
|
357
|
-
const genericErr = new ReaderApiError(
|
|
358
|
-
{
|
|
359
|
-
code: "internal_error",
|
|
360
|
-
message: `Request failed with status ${res.status}`
|
|
361
|
-
},
|
|
362
|
-
res.status,
|
|
363
|
-
requestId
|
|
364
|
-
);
|
|
365
|
-
if (res.status < 500) throw genericErr;
|
|
366
|
-
lastError = genericErr;
|
|
367
|
-
}
|
|
368
|
-
} else {
|
|
369
|
-
return parsed;
|
|
370
|
-
}
|
|
371
|
-
} catch (err) {
|
|
372
|
-
if (err instanceof ReaderApiError) {
|
|
373
|
-
if (err.httpStatus < 500 && err.httpStatus !== 429) throw err;
|
|
374
|
-
lastError = err;
|
|
375
|
-
} else if (err instanceof Error) {
|
|
376
|
-
if (err.name === "AbortError") {
|
|
377
|
-
lastError = new ReaderApiError(
|
|
378
|
-
{ code: "scrape_timeout", message: "Request timed out" },
|
|
379
|
-
504
|
|
380
|
-
);
|
|
381
|
-
} else {
|
|
382
|
-
lastError = err;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
329
|
+
const controller = new AbortController();
|
|
330
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
331
|
+
let res;
|
|
332
|
+
try {
|
|
333
|
+
res = await fetch(url, {
|
|
334
|
+
method,
|
|
335
|
+
headers: {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
"x-api-key": this.apiKey,
|
|
338
|
+
...this.extraHeaders
|
|
339
|
+
},
|
|
340
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
341
|
+
signal: controller.signal
|
|
342
|
+
});
|
|
343
|
+
} catch (err) {
|
|
344
|
+
clearTimeout(timeoutId);
|
|
345
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
346
|
+
throw new ReaderApiError(
|
|
347
|
+
{ code: "scrape_timeout", message: "Request timed out" },
|
|
348
|
+
504
|
|
349
|
+
);
|
|
385
350
|
}
|
|
386
|
-
|
|
387
|
-
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
353
|
+
clearTimeout(timeoutId);
|
|
354
|
+
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
355
|
+
const parsed = await res.json().catch(() => null);
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
if (parsed && "error" in parsed && parsed.error) {
|
|
358
|
+
throw toReaderApiError(parsed.error, res.status, requestId);
|
|
388
359
|
}
|
|
360
|
+
throw new ReaderApiError(
|
|
361
|
+
{
|
|
362
|
+
code: "internal_error",
|
|
363
|
+
message: `Request failed with status ${res.status}`
|
|
364
|
+
},
|
|
365
|
+
res.status,
|
|
366
|
+
requestId
|
|
367
|
+
);
|
|
389
368
|
}
|
|
390
|
-
|
|
369
|
+
return parsed;
|
|
391
370
|
}
|
|
392
371
|
};
|
|
393
372
|
var SessionsAPI = class {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Typed error classes mirroring the reader-api error code catalog.\n *\n * The API returns a stable `code` field on every error response. The SDK\n * branches on that code and throws a specific subclass, so callers can\n * write:\n *\n * try {\n * await client.read({ url });\n * } catch (err) {\n * if (err instanceof InsufficientCreditsError) {\n * // err.required, err.available, err.resetAt\n * }\n * }\n *\n * There is one subclass per code in the catalog. Unknown codes fall through\n * to the base `ReaderApiError`.\n */\n\nexport type ReaderErrorCode =\n | \"invalid_request\"\n | \"unauthenticated\"\n | \"insufficient_credits\"\n | \"url_blocked\"\n | \"not_found\"\n | \"conflict\"\n | \"rate_limited\"\n | \"concurrency_limited\"\n | \"internal_error\"\n | \"upstream_unavailable\"\n | \"scrape_timeout\";\n\nexport interface ApiErrorBody {\n code: ReaderErrorCode | string;\n message: string;\n details?: Record<string, unknown>;\n docsUrl?: string;\n}\n\nexport class ReaderApiError extends Error {\n readonly code: string;\n readonly httpStatus: number;\n readonly details?: Record<string, unknown>;\n readonly docsUrl?: string;\n readonly requestId?: string;\n\n constructor(body: ApiErrorBody, httpStatus: number, requestId?: string) {\n super(body.message);\n this.name = \"ReaderApiError\";\n this.code = body.code;\n this.httpStatus = httpStatus;\n this.details = body.details;\n this.docsUrl = body.docsUrl;\n this.requestId = requestId;\n }\n}\n\nexport class InvalidRequestError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InvalidRequestError\";\n }\n}\n\nexport class UnauthenticatedError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UnauthenticatedError\";\n }\n}\n\nexport class InsufficientCreditsError extends ReaderApiError {\n readonly required?: number;\n readonly available?: number;\n readonly resetAt?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InsufficientCreditsError\";\n this.required = body.details?.required as number | undefined;\n this.available = body.details?.available as number | undefined;\n this.resetAt = body.details?.resetAt as string | undefined;\n }\n}\n\nexport class UrlBlockedError extends ReaderApiError {\n readonly url?: string;\n readonly reason?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UrlBlockedError\";\n this.url = body.details?.url as string | undefined;\n this.reason = body.details?.reason as string | undefined;\n }\n}\n\nexport class NotFoundError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ConflictError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConflictError\";\n }\n}\n\nexport class RateLimitedError extends ReaderApiError {\n readonly retryAfterSeconds?: number;\n readonly limit?: number;\n readonly windowSeconds?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"RateLimitedError\";\n this.retryAfterSeconds = body.details?.retryAfterSeconds as number | undefined;\n this.limit = body.details?.limit as number | undefined;\n this.windowSeconds = body.details?.windowSeconds as number | undefined;\n }\n}\n\nexport class ConcurrencyLimitedError extends ReaderApiError {\n readonly active?: number;\n readonly max?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConcurrencyLimitedError\";\n this.active = body.details?.active as number | undefined;\n this.max = body.details?.max as number | undefined;\n }\n}\n\nexport class InternalServerError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InternalServerError\";\n }\n}\n\nexport class UpstreamUnavailableError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UpstreamUnavailableError\";\n }\n}\n\nexport class ScrapeTimeoutError extends ReaderApiError {\n readonly timeoutMs?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ScrapeTimeoutError\";\n this.timeoutMs = body.details?.timeoutMs as number | undefined;\n }\n}\n\n/**\n * Construct the right error subclass from an error response body.\n * Unknown codes fall through to the base class.\n */\nexport function toReaderApiError(\n body: ApiErrorBody,\n httpStatus: number,\n requestId?: string,\n): ReaderApiError {\n switch (body.code) {\n case \"invalid_request\":\n return new InvalidRequestError(body, httpStatus, requestId);\n case \"unauthenticated\":\n return new UnauthenticatedError(body, httpStatus, requestId);\n case \"insufficient_credits\":\n return new InsufficientCreditsError(body, httpStatus, requestId);\n case \"url_blocked\":\n return new UrlBlockedError(body, httpStatus, requestId);\n case \"not_found\":\n return new NotFoundError(body, httpStatus, requestId);\n case \"conflict\":\n return new ConflictError(body, httpStatus, requestId);\n case \"rate_limited\":\n return new RateLimitedError(body, httpStatus, requestId);\n case \"concurrency_limited\":\n return new ConcurrencyLimitedError(body, httpStatus, requestId);\n case \"internal_error\":\n return new InternalServerError(body, httpStatus, requestId);\n case \"upstream_unavailable\":\n return new UpstreamUnavailableError(body, httpStatus, requestId);\n case \"scrape_timeout\":\n return new ScrapeTimeoutError(body, httpStatus, requestId);\n default:\n return new ReaderApiError(body, httpStatus, requestId);\n }\n}\n","/**\n * Reader SDK Client\n *\n * @example\n * import { ReaderClient } from \"@vakra-dev/reader-js\";\n *\n * const client = new ReaderClient({ apiKey: \"rdr_your_key\" });\n *\n * // Synchronous scrape (single URL)\n * const result = await client.read({ url: \"https://example.com\" });\n * if (result.kind === \"scrape\") {\n * console.log(result.data.markdown);\n * }\n *\n * // Batch (returns a completed Job with all results collected)\n * const batch = await client.read({ urls: [\"url1\", \"url2\"] });\n * if (batch.kind === \"job\") {\n * for (const page of batch.data.results) {\n * console.log(page.url, page.markdown?.length);\n * }\n * }\n */\n\nimport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n Job,\n Credits,\n Page,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n} from \"./types.js\";\nimport {\n toReaderApiError,\n ReaderApiError,\n ScrapeTimeoutError,\n RateLimitedError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.reader.dev\";\nconst DEFAULT_TIMEOUT = 60_000;\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_POLL_INTERVAL = 2_000;\nconst DEFAULT_POLL_TIMEOUT = 300_000; // 5 minutes\n\ninterface JobWithPagination {\n data: Job;\n pagination: { total: number; skip: number; limit: number; hasMore: boolean; next?: string };\n}\n\nexport class ReaderClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n private maxRetries: number;\n private extraHeaders: Record<string, string>;\n private _sessions: SessionsAPI | null = null;\n\n constructor(config: ReaderClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.extraHeaders = config.headers || {};\n }\n\n /**\n * Browser sessions API.\n *\n * @example\n * ```typescript\n * const session = await client.sessions.create();\n * const browser = await chromium.connectOverCDP(session.wsEndpoint);\n * // ... use Playwright ...\n * await client.sessions.stop(session.sessionId);\n * ```\n */\n get sessions(): SessionsAPI {\n if (!this._sessions) {\n this._sessions = new SessionsAPI(this.request.bind(this));\n }\n return this._sessions;\n }\n\n /**\n * Read (scrape, batch, or crawl) one or more URLs.\n *\n * - Single URL → sync scrape, returns immediately with `{ kind: \"scrape\", data }`\n * - Multiple URLs or URL + maxDepth/maxPages → async job; this method polls\n * until the job terminates and returns `{ kind: \"job\", data }`.\n */\n async read(params: ReadParams): Promise<ReadResult> {\n const envelope = await this.request<SuccessEnvelope<unknown>>(\n \"POST\",\n \"/v1/read\",\n params,\n );\n\n const data = envelope.data as Record<string, unknown>;\n\n // Async job response: data.id + data.status present, no markdown/html/metadata\n if (\n data &&\n typeof data === \"object\" &&\n \"status\" in data &&\n \"mode\" in data &&\n !(\"markdown\" in data) &&\n !(\"metadata\" in data)\n ) {\n const jobId = String((data as { id: unknown }).id);\n const job = await this.waitForJob(jobId);\n return { kind: \"job\", data: job };\n }\n\n // Synchronous scrape: data has markdown/html/metadata\n return { kind: \"scrape\", data: data as unknown as ScrapeResult };\n }\n\n /**\n * Get job status and a single page of results.\n */\n async getJob(\n jobId: string,\n opts?: { skip?: number; limit?: number },\n ): Promise<{ job: Job; hasMore: boolean; next?: string }> {\n const query = new URLSearchParams();\n if (opts?.skip !== undefined) query.set(\"skip\", String(opts.skip));\n if (opts?.limit !== undefined) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n\n const envelope = await this.request<JobWithPagination>(\n \"GET\",\n `/v1/jobs/${jobId}${qs ? `?${qs}` : \"\"}`,\n );\n\n return {\n job: envelope.data,\n hasMore: envelope.pagination.hasMore,\n next: envelope.pagination.next,\n };\n }\n\n /**\n * Fetch all job result pages by following pagination.\n */\n async getAllJobResults(jobId: string): Promise<Page[]> {\n const pages: Page[] = [];\n let skip = 0;\n const limit = 100;\n\n while (true) {\n const { job, hasMore } = await this.getJob(jobId, { skip, limit });\n pages.push(...(job.results ?? []));\n if (!hasMore) break;\n skip += limit;\n }\n\n return pages;\n }\n\n /**\n * Cancel a job. Throws `ConflictError` if the job is already terminal.\n */\n async cancelJob(jobId: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/jobs/${jobId}`);\n }\n\n /**\n * Retry the failed URLs in a job. Throws `InvalidRequestError` if no\n * failed URLs exist.\n */\n async retryJob(jobId: string): Promise<{ id: string; status: string; retrying: number }> {\n const envelope = await this.request<\n SuccessEnvelope<{ id: string; status: string; retrying: number }>\n >(\"POST\", `/v1/jobs/${jobId}/retry`);\n return envelope.data;\n }\n\n /**\n * Poll a job until it completes, fails, or is cancelled. Collects all\n * paginated results when complete.\n */\n async waitForJob(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): Promise<Job> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { limit: 1 });\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n if (job.status === \"completed\") {\n job.results = await this.getAllJobResults(jobId);\n }\n return job;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} polling timed out after ${timeout}ms`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Stream job results as they arrive via polling.\n *\n * @example\n * for await (const event of client.stream(jobId)) {\n * if (event.type === \"page\") console.log(event.data.url);\n * if (event.type === \"done\") break;\n * }\n */\n async *stream(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): AsyncGenerator<StreamEvent> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n let lastCompleted = 0;\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { skip: lastCompleted, limit: 100 });\n\n yield {\n type: \"progress\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n\n for (const page of job.results ?? []) {\n if (page.error) {\n yield { type: \"error\", url: page.url, error: page.error };\n } else {\n yield { type: \"page\", data: page };\n }\n lastCompleted += 1;\n }\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n yield {\n type: \"done\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n return;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} stream timed out`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Get the current credit balance for this workspace.\n */\n async getCredits(): Promise<Credits> {\n const envelope = await this.request<SuccessEnvelope<Credits>>(\"GET\", \"/v1/usage/credits\");\n return envelope.data;\n }\n\n // --- Internal ---\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = path.startsWith(\"http\") ? path : `${this.baseUrl}${path}`;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const res = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n ...this.extraHeaders,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const parsed = (await res.json().catch(() => null)) as\n | SuccessEnvelope<unknown>\n | PaginatedEnvelope<unknown>\n | ErrorEnvelope\n | null;\n\n if (!res.ok) {\n if (parsed && \"error\" in parsed && parsed.error) {\n const err = toReaderApiError(parsed.error, res.status, requestId);\n\n // Don't retry client errors except 429\n if (res.status < 500 && res.status !== 429) throw err;\n\n // Honor Retry-After from the rate-limited response\n if (err instanceof RateLimitedError && err.retryAfterSeconds) {\n await sleep(err.retryAfterSeconds * 1000);\n }\n\n lastError = err;\n } else {\n const genericErr = new ReaderApiError(\n {\n code: \"internal_error\",\n message: `Request failed with status ${res.status}`,\n },\n res.status,\n requestId,\n );\n if (res.status < 500) throw genericErr;\n lastError = genericErr;\n }\n } else {\n return parsed as unknown as T;\n }\n } catch (err) {\n if (err instanceof ReaderApiError) {\n if (err.httpStatus < 500 && err.httpStatus !== 429) throw err;\n lastError = err;\n } else if (err instanceof Error) {\n if (err.name === \"AbortError\") {\n lastError = new ReaderApiError(\n { code: \"scrape_timeout\", message: \"Request timed out\" },\n 504,\n );\n } else {\n lastError = err;\n }\n }\n }\n\n // Exponential backoff before retry\n if (attempt < this.maxRetries) {\n await sleep(Math.pow(2, attempt) * 1000);\n }\n }\n\n throw (\n lastError ??\n new ReaderApiError({ code: \"internal_error\", message: \"Request failed\" }, 500)\n );\n }\n}\n\n// ─── Sessions API ────────────────────────────────────────────────────\n\ntype RequestFn = <T>(method: string, path: string, body?: unknown) => Promise<T>;\n\nclass SessionsAPI {\n constructor(private request: RequestFn) {}\n\n /**\n * Create a browser session. Returns a CDP WebSocket URL for\n * Playwright/Puppeteer connection.\n */\n async create(params?: CreateSessionParams): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"POST\",\n \"/v1/sessions\",\n params ?? {},\n );\n return envelope.data;\n }\n\n /**\n * Get session status.\n */\n async get(sessionId: string): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"GET\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * Stop a browser session.\n */\n async stop(sessionId: string): Promise<StopSessionResult> {\n const envelope = await this.request<SuccessEnvelope<StopSessionResult>>(\n \"DELETE\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * List active sessions.\n */\n async list(): Promise<SessionInfo[]> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo[]>>(\n \"GET\",\n \"/v1/sessions\",\n );\n return envelope.data;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Typed error classes mirroring the reader-api error code catalog.\n *\n * The API returns a stable `code` field on every error response. The SDK\n * branches on that code and throws a specific subclass, so callers can\n * write:\n *\n * try {\n * await client.read({ url });\n * } catch (err) {\n * if (err instanceof InsufficientCreditsError) {\n * // err.required, err.available, err.resetAt\n * }\n * }\n *\n * There is one subclass per code in the catalog. Unknown codes fall through\n * to the base `ReaderApiError`.\n */\n\nexport type ReaderErrorCode =\n | \"invalid_request\"\n | \"unauthenticated\"\n | \"insufficient_credits\"\n | \"url_blocked\"\n | \"not_found\"\n | \"conflict\"\n | \"rate_limited\"\n | \"concurrency_limited\"\n | \"internal_error\"\n | \"upstream_unavailable\"\n | \"scrape_timeout\";\n\nexport interface ApiErrorBody {\n code: ReaderErrorCode | string;\n message: string;\n details?: Record<string, unknown>;\n docsUrl?: string;\n}\n\nexport class ReaderApiError extends Error {\n readonly code: string;\n readonly httpStatus: number;\n readonly details?: Record<string, unknown>;\n readonly docsUrl?: string;\n readonly requestId?: string;\n\n constructor(body: ApiErrorBody, httpStatus: number, requestId?: string) {\n super(body.message);\n this.name = \"ReaderApiError\";\n this.code = body.code;\n this.httpStatus = httpStatus;\n this.details = body.details;\n this.docsUrl = body.docsUrl;\n this.requestId = requestId;\n }\n}\n\nexport class InvalidRequestError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InvalidRequestError\";\n }\n}\n\nexport class UnauthenticatedError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UnauthenticatedError\";\n }\n}\n\nexport class InsufficientCreditsError extends ReaderApiError {\n readonly required?: number;\n readonly available?: number;\n readonly resetAt?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InsufficientCreditsError\";\n this.required = body.details?.required as number | undefined;\n this.available = body.details?.available as number | undefined;\n this.resetAt = body.details?.resetAt as string | undefined;\n }\n}\n\nexport class UrlBlockedError extends ReaderApiError {\n readonly url?: string;\n readonly reason?: string;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UrlBlockedError\";\n this.url = body.details?.url as string | undefined;\n this.reason = body.details?.reason as string | undefined;\n }\n}\n\nexport class NotFoundError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ConflictError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConflictError\";\n }\n}\n\nexport class RateLimitedError extends ReaderApiError {\n readonly retryAfterSeconds?: number;\n readonly limit?: number;\n readonly windowSeconds?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"RateLimitedError\";\n this.retryAfterSeconds = body.details?.retryAfterSeconds as number | undefined;\n this.limit = body.details?.limit as number | undefined;\n this.windowSeconds = body.details?.windowSeconds as number | undefined;\n }\n}\n\nexport class ConcurrencyLimitedError extends ReaderApiError {\n readonly active?: number;\n readonly max?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ConcurrencyLimitedError\";\n this.active = body.details?.active as number | undefined;\n this.max = body.details?.max as number | undefined;\n }\n}\n\nexport class InternalServerError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"InternalServerError\";\n }\n}\n\nexport class UpstreamUnavailableError extends ReaderApiError {\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"UpstreamUnavailableError\";\n }\n}\n\nexport class ScrapeTimeoutError extends ReaderApiError {\n readonly timeoutMs?: number;\n\n constructor(body: ApiErrorBody, status: number, requestId?: string) {\n super(body, status, requestId);\n this.name = \"ScrapeTimeoutError\";\n this.timeoutMs = body.details?.timeoutMs as number | undefined;\n }\n}\n\n/**\n * Construct the right error subclass from an error response body.\n * Unknown codes fall through to the base class.\n */\nexport function toReaderApiError(\n body: ApiErrorBody,\n httpStatus: number,\n requestId?: string,\n): ReaderApiError {\n switch (body.code) {\n case \"invalid_request\":\n return new InvalidRequestError(body, httpStatus, requestId);\n case \"unauthenticated\":\n return new UnauthenticatedError(body, httpStatus, requestId);\n case \"insufficient_credits\":\n return new InsufficientCreditsError(body, httpStatus, requestId);\n case \"url_blocked\":\n return new UrlBlockedError(body, httpStatus, requestId);\n case \"not_found\":\n return new NotFoundError(body, httpStatus, requestId);\n case \"conflict\":\n return new ConflictError(body, httpStatus, requestId);\n case \"rate_limited\":\n return new RateLimitedError(body, httpStatus, requestId);\n case \"concurrency_limited\":\n return new ConcurrencyLimitedError(body, httpStatus, requestId);\n case \"internal_error\":\n return new InternalServerError(body, httpStatus, requestId);\n case \"upstream_unavailable\":\n return new UpstreamUnavailableError(body, httpStatus, requestId);\n case \"scrape_timeout\":\n return new ScrapeTimeoutError(body, httpStatus, requestId);\n default:\n return new ReaderApiError(body, httpStatus, requestId);\n }\n}\n","/**\n * Reader SDK Client\n *\n * @example\n * import { ReaderClient } from \"@vakra-dev/reader-js\";\n *\n * const client = new ReaderClient({ apiKey: \"rdr_your_key\" });\n *\n * // Synchronous scrape (single URL)\n * const result = await client.read({ url: \"https://example.com\" });\n * if (result.kind === \"scrape\") {\n * console.log(result.data.markdown);\n * }\n *\n * // Batch (returns a completed Job with all results collected)\n * const batch = await client.read({ urls: [\"url1\", \"url2\"] });\n * if (batch.kind === \"job\") {\n * for (const page of batch.data.results) {\n * console.log(page.url, page.markdown?.length);\n * }\n * }\n */\n\nimport type {\n ReaderClientConfig,\n ReadParams,\n ReadResult,\n ScrapeResult,\n Job,\n Credits,\n Page,\n StreamEvent,\n SuccessEnvelope,\n PaginatedEnvelope,\n ErrorEnvelope,\n SessionInfo,\n CreateSessionParams,\n StopSessionResult,\n} from \"./types.js\";\nimport {\n toReaderApiError,\n ReaderApiError,\n ScrapeTimeoutError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.reader.dev\";\nconst DEFAULT_TIMEOUT = 60_000;\nconst DEFAULT_POLL_INTERVAL = 2_000;\nconst DEFAULT_POLL_TIMEOUT = 600_000; // 10 minutes\n\ninterface JobWithPagination {\n data: Job;\n pagination: { total: number; skip: number; limit: number; hasMore: boolean; next?: string };\n}\n\nexport class ReaderClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n private extraHeaders: Record<string, string>;\n private _sessions: SessionsAPI | null = null;\n\n constructor(config: ReaderClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.extraHeaders = config.headers || {};\n }\n\n /**\n * Browser sessions API.\n *\n * @example\n * ```typescript\n * const session = await client.sessions.create();\n * const browser = await chromium.connectOverCDP(session.wsEndpoint);\n * // ... use Playwright ...\n * await client.sessions.stop(session.sessionId);\n * ```\n */\n get sessions(): SessionsAPI {\n if (!this._sessions) {\n this._sessions = new SessionsAPI(this.request.bind(this));\n }\n return this._sessions;\n }\n\n /**\n * Read (scrape, batch, or crawl) one or more URLs.\n *\n * - Single URL → sync scrape, returns immediately with `{ kind: \"scrape\", data }`\n * - Multiple URLs or URL + maxDepth/maxPages → async job; this method polls\n * until the job terminates and returns `{ kind: \"job\", data }`.\n */\n async read(params: ReadParams): Promise<ReadResult> {\n const envelope = await this.request<SuccessEnvelope<unknown>>(\n \"POST\",\n \"/v1/read\",\n params,\n );\n\n const data = envelope.data as Record<string, unknown>;\n\n // Async job response: data.id + data.status present, no markdown/html/metadata\n if (\n data &&\n typeof data === \"object\" &&\n \"status\" in data &&\n \"mode\" in data &&\n !(\"markdown\" in data) &&\n !(\"metadata\" in data)\n ) {\n const jobId = String((data as { id: unknown }).id);\n const job = await this.waitForJob(jobId, {\n timeout: params.pollTimeout,\n });\n return { kind: \"job\", data: job };\n }\n\n // Synchronous scrape: data has markdown/html/metadata\n return { kind: \"scrape\", data: data as unknown as ScrapeResult };\n }\n\n /**\n * Get job status and a single page of results.\n */\n async getJob(\n jobId: string,\n opts?: { skip?: number; limit?: number },\n ): Promise<{ job: Job; hasMore: boolean; next?: string }> {\n const query = new URLSearchParams();\n if (opts?.skip !== undefined) query.set(\"skip\", String(opts.skip));\n if (opts?.limit !== undefined) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n\n const envelope = await this.request<JobWithPagination>(\n \"GET\",\n `/v1/jobs/${jobId}${qs ? `?${qs}` : \"\"}`,\n );\n\n return {\n job: envelope.data,\n hasMore: envelope.pagination.hasMore,\n next: envelope.pagination.next,\n };\n }\n\n /**\n * Fetch all job result pages by following pagination.\n */\n async getAllJobResults(jobId: string): Promise<Page[]> {\n const pages: Page[] = [];\n let skip = 0;\n const limit = 100;\n\n while (true) {\n const { job, hasMore } = await this.getJob(jobId, { skip, limit });\n pages.push(...(job.results ?? []));\n if (!hasMore) break;\n skip += limit;\n }\n\n return pages;\n }\n\n /**\n * Cancel a job. Throws `ConflictError` if the job is already terminal.\n */\n async cancelJob(jobId: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/jobs/${jobId}`);\n }\n\n /**\n * Retry the failed URLs in a job. Throws `InvalidRequestError` if no\n * failed URLs exist.\n */\n async retryJob(jobId: string): Promise<{ id: string; status: string; retrying: number }> {\n const envelope = await this.request<\n SuccessEnvelope<{ id: string; status: string; retrying: number }>\n >(\"POST\", `/v1/jobs/${jobId}/retry`);\n return envelope.data;\n }\n\n /**\n * Poll a job until it completes, fails, or is cancelled. Collects all\n * paginated results when complete.\n */\n async waitForJob(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): Promise<Job> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { limit: 1 });\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n if (job.status === \"completed\") {\n job.results = await this.getAllJobResults(jobId);\n }\n return job;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} polling timed out after ${timeout}ms`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Stream job results as they arrive via polling.\n *\n * @example\n * for await (const event of client.stream(jobId)) {\n * if (event.type === \"page\") console.log(event.data.url);\n * if (event.type === \"done\") break;\n * }\n */\n async *stream(\n jobId: string,\n options?: { pollInterval?: number; timeout?: number },\n ): AsyncGenerator<StreamEvent> {\n const interval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;\n const timeout = options?.timeout ?? DEFAULT_POLL_TIMEOUT;\n const start = Date.now();\n let lastCompleted = 0;\n\n while (Date.now() - start < timeout) {\n const { job } = await this.getJob(jobId, { skip: lastCompleted, limit: 100 });\n\n yield {\n type: \"progress\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n\n for (const page of job.results ?? []) {\n if (page.error) {\n yield { type: \"error\", url: page.url, error: page.error };\n } else {\n yield { type: \"page\", data: page };\n }\n lastCompleted += 1;\n }\n\n if (\n job.status === \"completed\" ||\n job.status === \"failed\" ||\n job.status === \"cancelled\"\n ) {\n yield {\n type: \"done\",\n completed: job.completed,\n total: job.total,\n status: job.status,\n };\n return;\n }\n\n await sleep(interval);\n }\n\n throw new ScrapeTimeoutError(\n {\n code: \"scrape_timeout\",\n message: `Job ${jobId} stream timed out`,\n details: { timeoutMs: timeout },\n },\n 504,\n );\n }\n\n /**\n * Get the current credit balance for this workspace.\n */\n async getCredits(): Promise<Credits> {\n const envelope = await this.request<SuccessEnvelope<Credits>>(\"GET\", \"/v1/usage/credits\");\n return envelope.data;\n }\n\n // --- Internal ---\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = path.startsWith(\"http\") ? path : `${this.baseUrl}${path}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n ...this.extraHeaders,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new ReaderApiError(\n { code: \"scrape_timeout\", message: \"Request timed out\" },\n 504,\n );\n }\n throw err;\n }\n\n clearTimeout(timeoutId);\n\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const parsed = (await res.json().catch(() => null)) as\n | SuccessEnvelope<unknown>\n | PaginatedEnvelope<unknown>\n | ErrorEnvelope\n | null;\n\n if (!res.ok) {\n if (parsed && \"error\" in parsed && parsed.error) {\n throw toReaderApiError(parsed.error, res.status, requestId);\n }\n throw new ReaderApiError(\n {\n code: \"internal_error\",\n message: `Request failed with status ${res.status}`,\n },\n res.status,\n requestId,\n );\n }\n\n return parsed as unknown as T;\n }\n}\n\n// ─── Sessions API ────────────────────────────────────────────────────\n\ntype RequestFn = <T>(method: string, path: string, body?: unknown) => Promise<T>;\n\nclass SessionsAPI {\n constructor(private request: RequestFn) {}\n\n /**\n * Create a browser session. Returns a CDP WebSocket URL for\n * Playwright/Puppeteer connection.\n */\n async create(params?: CreateSessionParams): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"POST\",\n \"/v1/sessions\",\n params ?? {},\n );\n return envelope.data;\n }\n\n /**\n * Get session status.\n */\n async get(sessionId: string): Promise<SessionInfo> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo>>(\n \"GET\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * Stop a browser session.\n */\n async stop(sessionId: string): Promise<StopSessionResult> {\n const envelope = await this.request<SuccessEnvelope<StopSessionResult>>(\n \"DELETE\",\n `/v1/sessions/${sessionId}`,\n );\n return envelope.data;\n }\n\n /**\n * List active sessions.\n */\n async list(): Promise<SessionInfo[]> {\n const envelope = await this.request<SuccessEnvelope<SessionInfo[]>>(\n \"GET\",\n \"/v1/sessions\",\n );\n return envelope.data;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|