wellmarked 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,173 @@
1
+ /**
2
+ * WellMarked client.
3
+ *
4
+ * The client is a thin, typed wrapper around the HTTP API. All endpoint
5
+ * methods are async — there is no separate sync/async split as in the
6
+ * Python SDK because JavaScript I/O is async by default.
7
+ *
8
+ * import { WellMarked } from "wellmarked";
9
+ *
10
+ * const wm = new WellMarked({ apiKey: "wm_..." });
11
+ * const result = await wm.extract("https://example.com/article");
12
+ * console.log(result.markdown);
13
+ *
14
+ * The API key can also be passed via the `WELLMARKED_API_KEY` environment
15
+ * variable (Node.js), in which case `new WellMarked()` is enough.
16
+ */
17
+ import { APIStatusError } from "./errors.js";
18
+ import { type BulkJob, type CrawlJob, type ExtractResult, type RotatedKey, type Usage } from "./models.js";
19
+ export interface WellMarkedOptions {
20
+ /**
21
+ * Your WellMarked API key (`wm_...`). Falls back to the
22
+ * `WELLMARKED_API_KEY` environment variable (Node.js only).
23
+ */
24
+ apiKey?: string;
25
+ /** API base URL. Override for testing. */
26
+ baseUrl?: string;
27
+ /** Per-request timeout, milliseconds. Defaults to 30000 (30s). */
28
+ timeoutMs?: number;
29
+ /**
30
+ * Bring your own `fetch`. Defaults to the global `fetch`. Useful for
31
+ * polyfills, custom agents/proxies, or test mocking.
32
+ */
33
+ fetch?: typeof fetch;
34
+ /**
35
+ * Extra headers sent on every request — useful for adding an internal
36
+ * correlation id, a custom user agent suffix, etc.
37
+ *
38
+ * Authorization / Content-Type / Accept are reserved and silently
39
+ * ignored if passed (the SDK manages those itself).
40
+ */
41
+ headers?: Record<string, string>;
42
+ }
43
+ export interface ExtractOptions {
44
+ /**
45
+ * Use Playwright to render JS-heavy pages. Requires a Pro/Enterprise
46
+ * plan AND `ENABLE_JS_RENDERING=true` on the API instance.
47
+ */
48
+ renderJs?: boolean;
49
+ }
50
+ export interface BulkOptions {
51
+ renderJs?: boolean;
52
+ }
53
+ export interface CrawlOptions {
54
+ /** Max BFS depth from the root. Defaults to 1. Must be >= 0. */
55
+ depth?: number;
56
+ renderJs?: boolean;
57
+ }
58
+ export interface WaitForJobOptions {
59
+ /** Milliseconds to sleep between polls. Defaults to 2000. */
60
+ pollIntervalMs?: number;
61
+ /** Total ms to wait before timing out. `null` waits forever. Defaults to 300000 (5 min). */
62
+ timeoutMs?: number | null;
63
+ }
64
+ export declare class WellMarked {
65
+ private apiKey;
66
+ private readonly baseUrl;
67
+ private readonly timeoutMs;
68
+ private readonly fetchImpl;
69
+ private readonly extraHeaders;
70
+ constructor(options?: WellMarkedOptions);
71
+ /**
72
+ * Extract clean Markdown from a single URL.
73
+ *
74
+ * Throws:
75
+ * - `RateLimitError` — monthly plan limit reached.
76
+ * - `UnprocessableEntityError` — `no_content`, `target_timeout`, or
77
+ * `js_rendering_disabled`.
78
+ * - `AuthenticationError` — missing or invalid API key.
79
+ */
80
+ extract(url: string, options?: ExtractOptions): Promise<ExtractResult>;
81
+ /**
82
+ * Submit a batch of URLs for concurrent extraction.
83
+ *
84
+ * Returns immediately with `status="queued"`. Poll with `getJob` or
85
+ * block with `waitForJob` to collect results.
86
+ *
87
+ * Throws:
88
+ * - `PermissionDeniedError` — `plan_not_supported` (Free tier).
89
+ * - `UnprocessableEntityError` — `bulk_cap_exceeded` (50 on Pro).
90
+ * - `RateLimitError` — would exceed remaining monthly quota.
91
+ */
92
+ bulk(urls: Iterable<string>, options?: BulkOptions): Promise<BulkJob>;
93
+ /**
94
+ * Polymorphic job lookup — works for both bulk and crawl jobs.
95
+ *
96
+ * Calls `GET /bulk/{jobId}` first, then inspects the response's `kind`
97
+ * discriminator field. If the job is actually a crawl, a second request
98
+ * to `GET /crawl/{jobId}` fetches the full crawl shape (with per-item
99
+ * depth and the truncated flags). Returns `BulkJob` or `CrawlJob`
100
+ * accordingly.
101
+ *
102
+ * Use `isCrawlJob(job)` (or check `job.kind === "crawl"`) to branch on
103
+ * crawl-specific behavior. The shared interface (`status`, `completed`,
104
+ * `total`, `results`, `done`) works on either type.
105
+ *
106
+ * Jobs are retained for 6 hours after completion.
107
+ */
108
+ getJob(jobId: string): Promise<BulkJob | CrawlJob>;
109
+ /**
110
+ * Block until a job reaches `status="done"` (or timeout). Works for both
111
+ * bulk and crawl jobs.
112
+ *
113
+ * The first call uses the polymorphic `getJob` to discover the job's
114
+ * kind. Subsequent polls go directly to the typed endpoint, so a crawl
115
+ * job only pays the dispatch round-trip once.
116
+ *
117
+ * Throws:
118
+ * - `Error` with message "did not finish within ..." — the job didn't
119
+ * finish before `timeoutMs` elapsed.
120
+ */
121
+ waitForJob(jobId: string, options?: WaitForJobOptions): Promise<BulkJob | CrawlJob>;
122
+ /**
123
+ * Crawl a site starting from `url`, BFS to `depth`.
124
+ *
125
+ * Returns immediately with `status="queued"`. Use `getJob` to poll, or
126
+ * `waitForJob` to block until done — both handle crawl and bulk jobIds
127
+ * transparently.
128
+ *
129
+ * Plan caps:
130
+ * - Free → `PermissionDeniedError` (`plan_not_supported`)
131
+ * - Pro → max depth 5, up to 1,000 pages per crawl
132
+ * - Enterprise → unlimited depth and pages
133
+ *
134
+ * Throws:
135
+ * - `PermissionDeniedError` — `plan_not_supported` (Free tier).
136
+ * - `UnprocessableEntityError` — `crawl_depth_exceeded`.
137
+ */
138
+ crawl(url: string, options?: CrawlOptions): Promise<CrawlJob>;
139
+ /**
140
+ * Add or replace a per-request header for the rest of this client's life.
141
+ *
142
+ * Authorization / Content-Type / Accept are reserved — calls that try
143
+ * to set those are silently ignored. To rotate the bearer token, use
144
+ * `rotateKey()`.
145
+ */
146
+ setHeader(name: string, value: string): void;
147
+ /** Remove a header previously added via `headers:` or `setHeader()`. */
148
+ removeHeader(name: string): void;
149
+ /**
150
+ * Return your usage for the current billing period.
151
+ *
152
+ * Does not count toward your monthly quota.
153
+ */
154
+ getUsage(): Promise<Usage>;
155
+ /**
156
+ * Mint a new API key. The current key is invalidated immediately.
157
+ *
158
+ * The new raw key is in the returned `apiKey` field — store it before
159
+ * discarding the result. There is no recovery flow.
160
+ *
161
+ * The client auto-swaps to the new key for subsequent requests.
162
+ *
163
+ * Does not count toward your monthly quota.
164
+ */
165
+ rotateKey(): Promise<RotatedKey>;
166
+ /**
167
+ * Internal: read the current API key. Exposed for tests.
168
+ * Not part of the public, semver-stable surface.
169
+ */
170
+ _getApiKey(): string;
171
+ private request;
172
+ }
173
+ export { APIStatusError };
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Exception hierarchy for the WellMarked SDK.
4
+ *
5
+ * Every HTTP error returned by the API is translated into a typed error
6
+ * whose class corresponds to the HTTP status and whose `code` matches the
7
+ * `error.code` field in the response body. Catch `WellMarkedError` for
8
+ * anything raised by the SDK; catch a more specific subclass when you want
9
+ * to handle one failure mode.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.InternalServerError = exports.RateLimitError = exports.UnprocessableEntityError = exports.NotFoundError = exports.PermissionDeniedError = exports.AuthenticationError = exports.APIStatusError = exports.APIConnectionError = exports.WellMarkedError = void 0;
13
+ exports.fromResponse = fromResponse;
14
+ class WellMarkedError extends Error {
15
+ constructor(message, options = {}) {
16
+ super(message);
17
+ this.name = "WellMarkedError";
18
+ this.code = options.code;
19
+ this.statusCode = options.statusCode;
20
+ this.retryAfter = options.retryAfter;
21
+ this.requestId = options.requestId;
22
+ if (options.cause !== undefined) {
23
+ this.cause = options.cause;
24
+ }
25
+ // Maintain proper prototype chain when transpiled to ES5.
26
+ Object.setPrototypeOf(this, new.target.prototype);
27
+ }
28
+ }
29
+ exports.WellMarkedError = WellMarkedError;
30
+ /** Raised when the SDK couldn't reach the API (DNS, TCP, TLS, timeout). */
31
+ class APIConnectionError extends WellMarkedError {
32
+ constructor(message, options = {}) {
33
+ super(message, options);
34
+ this.name = "APIConnectionError";
35
+ Object.setPrototypeOf(this, APIConnectionError.prototype);
36
+ }
37
+ }
38
+ exports.APIConnectionError = APIConnectionError;
39
+ /** Raised for any non-2xx response from the API. */
40
+ class APIStatusError extends WellMarkedError {
41
+ constructor(message, options = {}) {
42
+ super(message, options);
43
+ this.name = "APIStatusError";
44
+ Object.setPrototypeOf(this, APIStatusError.prototype);
45
+ }
46
+ }
47
+ exports.APIStatusError = APIStatusError;
48
+ /** 401 — missing or invalid API key. */
49
+ class AuthenticationError extends APIStatusError {
50
+ constructor(message, options = {}) {
51
+ super(message, options);
52
+ this.name = "AuthenticationError";
53
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
54
+ }
55
+ }
56
+ exports.AuthenticationError = AuthenticationError;
57
+ /** 403 — account inactive, plan does not allow this operation, or job belongs to another user. */
58
+ class PermissionDeniedError extends APIStatusError {
59
+ constructor(message, options = {}) {
60
+ super(message, options);
61
+ this.name = "PermissionDeniedError";
62
+ Object.setPrototypeOf(this, PermissionDeniedError.prototype);
63
+ }
64
+ }
65
+ exports.PermissionDeniedError = PermissionDeniedError;
66
+ /** 404 — job not found or expired past the 6-hour retention window. */
67
+ class NotFoundError extends APIStatusError {
68
+ constructor(message, options = {}) {
69
+ super(message, options);
70
+ this.name = "NotFoundError";
71
+ Object.setPrototypeOf(this, NotFoundError.prototype);
72
+ }
73
+ }
74
+ exports.NotFoundError = NotFoundError;
75
+ /**
76
+ * 422 — request was syntactically valid but couldn't be fulfilled.
77
+ *
78
+ * Common `code` values:
79
+ * - `no_content` — could not identify main content on the page
80
+ * - `target_timeout` — the target URL timed out
81
+ * - `js_rendering_disabled` — `renderJs=true` but the server has it off
82
+ * - `bulk_cap_exceeded` — more URLs than the plan allows per request
83
+ * - `crawl_depth_exceeded` — requested depth above the plan cap
84
+ */
85
+ class UnprocessableEntityError extends APIStatusError {
86
+ constructor(message, options = {}) {
87
+ super(message, options);
88
+ this.name = "UnprocessableEntityError";
89
+ Object.setPrototypeOf(this, UnprocessableEntityError.prototype);
90
+ }
91
+ }
92
+ exports.UnprocessableEntityError = UnprocessableEntityError;
93
+ /** 429 — monthly plan limit reached. `retryAfter` is the number of seconds until reset. */
94
+ class RateLimitError extends APIStatusError {
95
+ constructor(message, options = {}) {
96
+ super(message, options);
97
+ this.name = "RateLimitError";
98
+ Object.setPrototypeOf(this, RateLimitError.prototype);
99
+ }
100
+ }
101
+ exports.RateLimitError = RateLimitError;
102
+ /** 5xx — something went wrong on the API side. */
103
+ class InternalServerError extends APIStatusError {
104
+ constructor(message, options = {}) {
105
+ super(message, options);
106
+ this.name = "InternalServerError";
107
+ Object.setPrototypeOf(this, InternalServerError.prototype);
108
+ }
109
+ }
110
+ exports.InternalServerError = InternalServerError;
111
+ const STATUS_TO_EXC = {
112
+ 401: AuthenticationError,
113
+ 403: PermissionDeniedError,
114
+ 404: NotFoundError,
115
+ 422: UnprocessableEntityError,
116
+ 429: RateLimitError,
117
+ };
118
+ /** Build the right error subclass for a given HTTP status + JSON body. */
119
+ function fromResponse(statusCode, body, requestId) {
120
+ let code;
121
+ let message = `HTTP ${statusCode}`;
122
+ let retryAfter;
123
+ if (body && typeof body === "object" && "error" in body) {
124
+ const err = body.error;
125
+ if (err && typeof err === "object") {
126
+ const e = err;
127
+ if (typeof e.code === "string")
128
+ code = e.code;
129
+ if (typeof e.message === "string")
130
+ message = e.message;
131
+ if (typeof e.retry_after === "number")
132
+ retryAfter = e.retry_after;
133
+ }
134
+ }
135
+ let Ctor;
136
+ if (statusCode >= 500) {
137
+ Ctor = InternalServerError;
138
+ }
139
+ else {
140
+ Ctor = STATUS_TO_EXC[statusCode] ?? APIStatusError;
141
+ }
142
+ return new Ctor(message, { code, statusCode, retryAfter, requestId });
143
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Exception hierarchy for the WellMarked SDK.
3
+ *
4
+ * Every HTTP error returned by the API is translated into a typed error
5
+ * whose class corresponds to the HTTP status and whose `code` matches the
6
+ * `error.code` field in the response body. Catch `WellMarkedError` for
7
+ * anything raised by the SDK; catch a more specific subclass when you want
8
+ * to handle one failure mode.
9
+ */
10
+ export interface WellMarkedErrorOptions {
11
+ code?: string | undefined;
12
+ statusCode?: number | undefined;
13
+ retryAfter?: number | undefined;
14
+ requestId?: string | undefined;
15
+ cause?: unknown;
16
+ }
17
+ export declare class WellMarkedError extends Error {
18
+ readonly code: string | undefined;
19
+ readonly statusCode: number | undefined;
20
+ readonly retryAfter: number | undefined;
21
+ readonly requestId: string | undefined;
22
+ constructor(message: string, options?: WellMarkedErrorOptions);
23
+ }
24
+ /** Raised when the SDK couldn't reach the API (DNS, TCP, TLS, timeout). */
25
+ export declare class APIConnectionError extends WellMarkedError {
26
+ constructor(message: string, options?: WellMarkedErrorOptions);
27
+ }
28
+ /** Raised for any non-2xx response from the API. */
29
+ export declare class APIStatusError extends WellMarkedError {
30
+ constructor(message: string, options?: WellMarkedErrorOptions);
31
+ }
32
+ /** 401 — missing or invalid API key. */
33
+ export declare class AuthenticationError extends APIStatusError {
34
+ constructor(message: string, options?: WellMarkedErrorOptions);
35
+ }
36
+ /** 403 — account inactive, plan does not allow this operation, or job belongs to another user. */
37
+ export declare class PermissionDeniedError extends APIStatusError {
38
+ constructor(message: string, options?: WellMarkedErrorOptions);
39
+ }
40
+ /** 404 — job not found or expired past the 6-hour retention window. */
41
+ export declare class NotFoundError extends APIStatusError {
42
+ constructor(message: string, options?: WellMarkedErrorOptions);
43
+ }
44
+ /**
45
+ * 422 — request was syntactically valid but couldn't be fulfilled.
46
+ *
47
+ * Common `code` values:
48
+ * - `no_content` — could not identify main content on the page
49
+ * - `target_timeout` — the target URL timed out
50
+ * - `js_rendering_disabled` — `renderJs=true` but the server has it off
51
+ * - `bulk_cap_exceeded` — more URLs than the plan allows per request
52
+ * - `crawl_depth_exceeded` — requested depth above the plan cap
53
+ */
54
+ export declare class UnprocessableEntityError extends APIStatusError {
55
+ constructor(message: string, options?: WellMarkedErrorOptions);
56
+ }
57
+ /** 429 — monthly plan limit reached. `retryAfter` is the number of seconds until reset. */
58
+ export declare class RateLimitError extends APIStatusError {
59
+ constructor(message: string, options?: WellMarkedErrorOptions);
60
+ }
61
+ /** 5xx — something went wrong on the API side. */
62
+ export declare class InternalServerError extends APIStatusError {
63
+ constructor(message: string, options?: WellMarkedErrorOptions);
64
+ }
65
+ /** Build the right error subclass for a given HTTP status + JSON body. */
66
+ export declare function fromResponse(statusCode: number, body: unknown, requestId?: string): APIStatusError;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WellMarkedError = exports.UnprocessableEntityError = exports.RateLimitError = exports.PermissionDeniedError = exports.NotFoundError = exports.InternalServerError = exports.AuthenticationError = exports.APIStatusError = exports.APIConnectionError = exports.isCrawlJob = exports.isBulkJob = exports.WellMarked = exports.VERSION = void 0;
4
+ /**
5
+ * Official JavaScript/TypeScript SDK for the WellMarked API.
6
+ *
7
+ * import { WellMarked } from "wellmarked";
8
+ *
9
+ * const wm = new WellMarked({ apiKey: "wm_..." });
10
+ * const result = await wm.extract("https://example.com/article");
11
+ * console.log(result.markdown);
12
+ *
13
+ * See https://wellmarked.io/docs for the full API reference.
14
+ */
15
+ var version_js_1 = require("./version.cjs");
16
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_js_1.VERSION; } });
17
+ var client_js_1 = require("./client.cjs");
18
+ Object.defineProperty(exports, "WellMarked", { enumerable: true, get: function () { return client_js_1.WellMarked; } });
19
+ var models_js_1 = require("./models.cjs");
20
+ Object.defineProperty(exports, "isBulkJob", { enumerable: true, get: function () { return models_js_1.isBulkJob; } });
21
+ Object.defineProperty(exports, "isCrawlJob", { enumerable: true, get: function () { return models_js_1.isCrawlJob; } });
22
+ var errors_js_1 = require("./errors.cjs");
23
+ Object.defineProperty(exports, "APIConnectionError", { enumerable: true, get: function () { return errors_js_1.APIConnectionError; } });
24
+ Object.defineProperty(exports, "APIStatusError", { enumerable: true, get: function () { return errors_js_1.APIStatusError; } });
25
+ Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_js_1.AuthenticationError; } });
26
+ Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return errors_js_1.InternalServerError; } });
27
+ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_js_1.NotFoundError; } });
28
+ Object.defineProperty(exports, "PermissionDeniedError", { enumerable: true, get: function () { return errors_js_1.PermissionDeniedError; } });
29
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_1.RateLimitError; } });
30
+ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, get: function () { return errors_js_1.UnprocessableEntityError; } });
31
+ Object.defineProperty(exports, "WellMarkedError", { enumerable: true, get: function () { return errors_js_1.WellMarkedError; } });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Official JavaScript/TypeScript SDK for the WellMarked API.
3
+ *
4
+ * import { WellMarked } from "wellmarked";
5
+ *
6
+ * const wm = new WellMarked({ apiKey: "wm_..." });
7
+ * const result = await wm.extract("https://example.com/article");
8
+ * console.log(result.markdown);
9
+ *
10
+ * See https://wellmarked.io/docs for the full API reference.
11
+ */
12
+ export { VERSION } from "./version.js";
13
+ export { WellMarked, type WellMarkedOptions, type ExtractOptions, type BulkOptions, type CrawlOptions, type WaitForJobOptions, } from "./client.js";
14
+ export { type BulkItem, type BulkJob, type CrawlItem, type CrawlJob, type ExtractionMeta, type ExtractResult, type JobStatus, type RotatedKey, type TruncatedReason, type Usage, isBulkJob, isCrawlJob, } from "./models.js";
15
+ export { APIConnectionError, APIStatusError, AuthenticationError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, UnprocessableEntityError, WellMarkedError, } from "./errors.js";
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /**
3
+ * Typed response objects returned by the WellMarked SDK.
4
+ *
5
+ * These mirror the JSON shapes documented at https://api.wellmarked.io/docs.
6
+ *
7
+ * Response objects carry only the body fields documented in the API
8
+ * reference — they do not surface HTTP headers. Quota state lives on the
9
+ * account, so use `WellMarked.getUsage()` to read it.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.extractionMetaFromDict = extractionMetaFromDict;
13
+ exports.extractResultFromResponse = extractResultFromResponse;
14
+ exports.bulkItemFromDict = bulkItemFromDict;
15
+ exports.bulkJobFromResponse = bulkJobFromResponse;
16
+ exports.crawlItemFromDict = crawlItemFromDict;
17
+ exports.crawlJobFromResponse = crawlJobFromResponse;
18
+ exports.usageFromResponse = usageFromResponse;
19
+ exports.rotatedKeyFromResponse = rotatedKeyFromResponse;
20
+ exports.isBulkJob = isBulkJob;
21
+ exports.isCrawlJob = isCrawlJob;
22
+ function parseDate(value) {
23
+ if (value === null || value === undefined)
24
+ return null;
25
+ if (value instanceof Date)
26
+ return value;
27
+ if (typeof value !== "string")
28
+ return null;
29
+ const d = new Date(value);
30
+ return Number.isNaN(d.getTime()) ? null : d;
31
+ }
32
+ function extractionMetaFromDict(data) {
33
+ return {
34
+ url: typeof data.url === "string" ? data.url : "",
35
+ title: typeof data.title === "string" ? data.title : null,
36
+ author: typeof data.author === "string" ? data.author : null,
37
+ date: typeof data.date === "string" ? data.date : null,
38
+ retrievedAt: parseDate(data.retrieved_at),
39
+ };
40
+ }
41
+ function extractResultFromResponse(body) {
42
+ const rawMeta = body.metadata && typeof body.metadata === "object"
43
+ ? body.metadata
44
+ : {};
45
+ return {
46
+ markdown: typeof body.markdown === "string" ? body.markdown : "",
47
+ metadata: extractionMetaFromDict(rawMeta),
48
+ requestId: typeof body.request_id === "string" ? body.request_id : "",
49
+ };
50
+ }
51
+ function bulkItemFromDict(data) {
52
+ const rawMeta = data.metadata;
53
+ const url = typeof data.url === "string" ? data.url : "";
54
+ const markdown = typeof data.markdown === "string" ? data.markdown : null;
55
+ const error = typeof data.error === "string" ? data.error : null;
56
+ const metadata = rawMeta && typeof rawMeta === "object"
57
+ ? extractionMetaFromDict(rawMeta)
58
+ : null;
59
+ return {
60
+ url,
61
+ markdown,
62
+ metadata,
63
+ error,
64
+ get ok() {
65
+ return error === null && markdown !== null;
66
+ },
67
+ };
68
+ }
69
+ function bulkJobFromResponse(body) {
70
+ const rawResults = Array.isArray(body.results) ? body.results : [];
71
+ const status = typeof body.status === "string" ? body.status : "queued";
72
+ const jobId = typeof body.job_id === "string" ? body.job_id : "";
73
+ const total = typeof body.total === "number" ? body.total : 0;
74
+ const completed = typeof body.completed === "number" ? body.completed : 0;
75
+ const createdAt = parseDate(body.created_at);
76
+ const finishedAt = parseDate(body.finished_at);
77
+ const results = rawResults
78
+ .filter((r) => r !== null && typeof r === "object")
79
+ .map(bulkItemFromDict);
80
+ return {
81
+ kind: "bulk",
82
+ jobId,
83
+ status,
84
+ total,
85
+ completed,
86
+ results,
87
+ createdAt,
88
+ finishedAt,
89
+ get done() {
90
+ return status === "done";
91
+ },
92
+ };
93
+ }
94
+ function crawlItemFromDict(data) {
95
+ const rawMeta = data.metadata;
96
+ const url = typeof data.url === "string" ? data.url : "";
97
+ const depth = typeof data.depth === "number" ? data.depth : 0;
98
+ const markdown = typeof data.markdown === "string" ? data.markdown : null;
99
+ const error = typeof data.error === "string" ? data.error : null;
100
+ const metadata = rawMeta && typeof rawMeta === "object"
101
+ ? extractionMetaFromDict(rawMeta)
102
+ : null;
103
+ return {
104
+ url,
105
+ depth,
106
+ markdown,
107
+ metadata,
108
+ error,
109
+ get ok() {
110
+ return error === null && markdown !== null;
111
+ },
112
+ };
113
+ }
114
+ function crawlJobFromResponse(body) {
115
+ const rawResults = Array.isArray(body.results) ? body.results : [];
116
+ const status = typeof body.status === "string" ? body.status : "queued";
117
+ const jobId = typeof body.job_id === "string" ? body.job_id : "";
118
+ const total = typeof body.total === "number" ? body.total : 0;
119
+ const completed = typeof body.completed === "number" ? body.completed : 0;
120
+ const createdAt = parseDate(body.created_at);
121
+ const finishedAt = parseDate(body.finished_at);
122
+ const truncated = body.truncated === true;
123
+ const truncatedReason = typeof body.truncated_reason === "string"
124
+ ? body.truncated_reason
125
+ : null;
126
+ const results = rawResults
127
+ .filter((r) => r !== null && typeof r === "object")
128
+ .map(crawlItemFromDict);
129
+ return {
130
+ kind: "crawl",
131
+ jobId,
132
+ status,
133
+ total,
134
+ completed,
135
+ results,
136
+ truncated,
137
+ truncatedReason,
138
+ createdAt,
139
+ finishedAt,
140
+ get done() {
141
+ return status === "done";
142
+ },
143
+ };
144
+ }
145
+ function usageFromResponse(body) {
146
+ return {
147
+ plan: typeof body.plan === "string" ? body.plan : "",
148
+ period: typeof body.period === "string" ? body.period : "",
149
+ used: typeof body.used === "number" ? body.used : 0,
150
+ limit: typeof body.limit === "number" ? body.limit : 0,
151
+ remaining: typeof body.remaining === "number" ? body.remaining : 0,
152
+ };
153
+ }
154
+ function rotatedKeyFromResponse(body) {
155
+ return {
156
+ apiKey: typeof body.api_key === "string" ? body.api_key : "",
157
+ rotatedAt: parseDate(body.rotated_at),
158
+ };
159
+ }
160
+ // ── Type guards ──────────────────────────────────────────────────────────────
161
+ function isBulkJob(job) {
162
+ return job.kind === "bulk";
163
+ }
164
+ function isCrawlJob(job) {
165
+ return job.kind === "crawl";
166
+ }