@vakra-dev/reader-js 0.3.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 +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -175,7 +175,7 @@ function toReaderApiError(body, httpStatus, requestId) {
|
|
|
175
175
|
var DEFAULT_BASE_URL = "https://api.reader.dev";
|
|
176
176
|
var DEFAULT_TIMEOUT = 6e4;
|
|
177
177
|
var DEFAULT_POLL_INTERVAL = 2e3;
|
|
178
|
-
var DEFAULT_POLL_TIMEOUT =
|
|
178
|
+
var DEFAULT_POLL_TIMEOUT = 6e5;
|
|
179
179
|
var ReaderClient = class {
|
|
180
180
|
apiKey;
|
|
181
181
|
baseUrl;
|
|
@@ -224,7 +224,9 @@ var ReaderClient = class {
|
|
|
224
224
|
const data = envelope.data;
|
|
225
225
|
if (data && typeof data === "object" && "status" in data && "mode" in data && !("markdown" in data) && !("metadata" in data)) {
|
|
226
226
|
const jobId = String(data.id);
|
|
227
|
-
const job = await this.waitForJob(jobId
|
|
227
|
+
const job = await this.waitForJob(jobId, {
|
|
228
|
+
timeout: params.pollTimeout
|
|
229
|
+
});
|
|
228
230
|
return { kind: "job", data: job };
|
|
229
231
|
}
|
|
230
232
|
return { kind: "scrape", data };
|
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} 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 = 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 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 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":[]}
|
|
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
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -136,7 +136,7 @@ function toReaderApiError(body, httpStatus, requestId) {
|
|
|
136
136
|
var DEFAULT_BASE_URL = "https://api.reader.dev";
|
|
137
137
|
var DEFAULT_TIMEOUT = 6e4;
|
|
138
138
|
var DEFAULT_POLL_INTERVAL = 2e3;
|
|
139
|
-
var DEFAULT_POLL_TIMEOUT =
|
|
139
|
+
var DEFAULT_POLL_TIMEOUT = 6e5;
|
|
140
140
|
var ReaderClient = class {
|
|
141
141
|
apiKey;
|
|
142
142
|
baseUrl;
|
|
@@ -185,7 +185,9 @@ var ReaderClient = class {
|
|
|
185
185
|
const data = envelope.data;
|
|
186
186
|
if (data && typeof data === "object" && "status" in data && "mode" in data && !("markdown" in data) && !("metadata" in data)) {
|
|
187
187
|
const jobId = String(data.id);
|
|
188
|
-
const job = await this.waitForJob(jobId
|
|
188
|
+
const job = await this.waitForJob(jobId, {
|
|
189
|
+
timeout: params.pollTimeout
|
|
190
|
+
});
|
|
189
191
|
return { kind: "job", data: job };
|
|
190
192
|
}
|
|
191
193
|
return { kind: "scrape", data };
|
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} 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 = 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 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 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":[]}
|
|
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":[]}
|