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.
package/src/models.ts ADDED
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Typed response objects returned by the WellMarked SDK.
3
+ *
4
+ * These mirror the JSON shapes documented at https://api.wellmarked.io/docs.
5
+ *
6
+ * Response objects carry only the body fields documented in the API
7
+ * reference — they do not surface HTTP headers. Quota state lives on the
8
+ * account, so use `WellMarked.getUsage()` to read it.
9
+ */
10
+
11
+ function parseDate(value: unknown): Date | null {
12
+ if (value === null || value === undefined) return null;
13
+ if (value instanceof Date) return value;
14
+ if (typeof value !== "string") return null;
15
+ const d = new Date(value);
16
+ return Number.isNaN(d.getTime()) ? null : d;
17
+ }
18
+
19
+ // ── Extraction ───────────────────────────────────────────────────────────────
20
+
21
+ /**
22
+ * Per-article metadata returned with each extraction.
23
+ *
24
+ * `date` is the article's published date as a string (often `null` — not
25
+ * every page publishes one). `retrievedAt` is the timestamp at which
26
+ * WellMarked actually fetched the page, populated on every successful
27
+ * extraction. The two fields are independent.
28
+ */
29
+ export interface ExtractionMeta {
30
+ url: string;
31
+ title: string | null;
32
+ author: string | null;
33
+ date: string | null;
34
+ retrievedAt: Date | null;
35
+ }
36
+
37
+ export function extractionMetaFromDict(data: Record<string, unknown>): ExtractionMeta {
38
+ return {
39
+ url: typeof data.url === "string" ? data.url : "",
40
+ title: typeof data.title === "string" ? data.title : null,
41
+ author: typeof data.author === "string" ? data.author : null,
42
+ date: typeof data.date === "string" ? data.date : null,
43
+ retrievedAt: parseDate(data.retrieved_at),
44
+ };
45
+ }
46
+
47
+ /** Result of `POST /extract`. */
48
+ export interface ExtractResult {
49
+ markdown: string;
50
+ metadata: ExtractionMeta;
51
+ requestId: string;
52
+ }
53
+
54
+ export function extractResultFromResponse(body: Record<string, unknown>): ExtractResult {
55
+ const rawMeta =
56
+ body.metadata && typeof body.metadata === "object"
57
+ ? (body.metadata as Record<string, unknown>)
58
+ : {};
59
+ return {
60
+ markdown: typeof body.markdown === "string" ? body.markdown : "",
61
+ metadata: extractionMetaFromDict(rawMeta),
62
+ requestId: typeof body.request_id === "string" ? body.request_id : "",
63
+ };
64
+ }
65
+
66
+ // ── Bulk ─────────────────────────────────────────────────────────────────────
67
+
68
+ /**
69
+ * One entry in a bulk job's `results` list.
70
+ *
71
+ * On success, `markdown` and `metadata` are populated and `error` is null.
72
+ * On a per-URL failure, `markdown`/`metadata` are null and `error` carries
73
+ * an API error code (e.g. `target_timeout`).
74
+ */
75
+ export interface BulkItem {
76
+ url: string;
77
+ markdown: string | null;
78
+ metadata: ExtractionMeta | null;
79
+ error: string | null;
80
+ /** True when the item completed successfully (no error + has markdown). */
81
+ readonly ok: boolean;
82
+ }
83
+
84
+ export function bulkItemFromDict(data: Record<string, unknown>): BulkItem {
85
+ const rawMeta = data.metadata;
86
+ const url = typeof data.url === "string" ? data.url : "";
87
+ const markdown = typeof data.markdown === "string" ? data.markdown : null;
88
+ const error = typeof data.error === "string" ? data.error : null;
89
+ const metadata =
90
+ rawMeta && typeof rawMeta === "object"
91
+ ? extractionMetaFromDict(rawMeta as Record<string, unknown>)
92
+ : null;
93
+ return {
94
+ url,
95
+ markdown,
96
+ metadata,
97
+ error,
98
+ get ok(): boolean {
99
+ return error === null && markdown !== null;
100
+ },
101
+ };
102
+ }
103
+
104
+ /** "queued" | "processing" | "done" */
105
+ export type JobStatus = "queued" | "processing" | "done";
106
+
107
+ /**
108
+ * Status of a bulk extraction job.
109
+ *
110
+ * Returned from both `POST /bulk` and `GET /bulk/{jobId}`. Also one of the
111
+ * two possible return types from the polymorphic `WellMarked.getJob` and
112
+ * `WellMarked.waitForJob` — use the `kind` discriminator (or a type guard)
113
+ * to distinguish from `CrawlJob` if you need to read crawl-specific fields.
114
+ */
115
+ export interface BulkJob {
116
+ readonly kind: "bulk";
117
+ jobId: string;
118
+ status: JobStatus;
119
+ total: number;
120
+ completed: number;
121
+ results: BulkItem[];
122
+ createdAt: Date | null;
123
+ finishedAt: Date | null;
124
+ /** True when `status === "done"`. */
125
+ readonly done: boolean;
126
+ }
127
+
128
+ export function bulkJobFromResponse(body: Record<string, unknown>): BulkJob {
129
+ const rawResults = Array.isArray(body.results) ? body.results : [];
130
+ const status =
131
+ typeof body.status === "string" ? (body.status as JobStatus) : "queued";
132
+ const jobId = typeof body.job_id === "string" ? body.job_id : "";
133
+ const total = typeof body.total === "number" ? body.total : 0;
134
+ const completed = typeof body.completed === "number" ? body.completed : 0;
135
+ const createdAt = parseDate(body.created_at);
136
+ const finishedAt = parseDate(body.finished_at);
137
+ const results = rawResults
138
+ .filter((r): r is Record<string, unknown> => r !== null && typeof r === "object")
139
+ .map(bulkItemFromDict);
140
+ return {
141
+ kind: "bulk",
142
+ jobId,
143
+ status,
144
+ total,
145
+ completed,
146
+ results,
147
+ createdAt,
148
+ finishedAt,
149
+ get done(): boolean {
150
+ return status === "done";
151
+ },
152
+ };
153
+ }
154
+
155
+ // ── Crawl ────────────────────────────────────────────────────────────────────
156
+
157
+ /**
158
+ * One page in a crawl job's `results` list.
159
+ *
160
+ * Shape mirrors `BulkItem` with an added `depth` field showing how far
161
+ * from the root URL this page sits in the BFS.
162
+ */
163
+ export interface CrawlItem {
164
+ url: string;
165
+ depth: number;
166
+ markdown: string | null;
167
+ metadata: ExtractionMeta | null;
168
+ error: string | null;
169
+ /** True when the page completed successfully (no error + has markdown). */
170
+ readonly ok: boolean;
171
+ }
172
+
173
+ export function crawlItemFromDict(data: Record<string, unknown>): CrawlItem {
174
+ const rawMeta = data.metadata;
175
+ const url = typeof data.url === "string" ? data.url : "";
176
+ const depth = typeof data.depth === "number" ? data.depth : 0;
177
+ const markdown = typeof data.markdown === "string" ? data.markdown : null;
178
+ const error = typeof data.error === "string" ? data.error : null;
179
+ const metadata =
180
+ rawMeta && typeof rawMeta === "object"
181
+ ? extractionMetaFromDict(rawMeta as Record<string, unknown>)
182
+ : null;
183
+ return {
184
+ url,
185
+ depth,
186
+ markdown,
187
+ metadata,
188
+ error,
189
+ get ok(): boolean {
190
+ return error === null && markdown !== null;
191
+ },
192
+ };
193
+ }
194
+
195
+ export type TruncatedReason = "page_cap_reached" | "quota_exhausted";
196
+
197
+ /**
198
+ * Status of a crawl job. Returned from `POST /crawl` and `GET /crawl/{jobId}`.
199
+ *
200
+ * Two crawl-only fields:
201
+ * - `truncated` — true when the crawl stopped before exhausting
202
+ * the frontier (depth/page cap or quota).
203
+ * - `truncatedReason` — `"page_cap_reached"` | `"quota_exhausted"` |
204
+ * `null`.
205
+ */
206
+ export interface CrawlJob {
207
+ readonly kind: "crawl";
208
+ jobId: string;
209
+ status: JobStatus;
210
+ total: number;
211
+ completed: number;
212
+ results: CrawlItem[];
213
+ truncated: boolean;
214
+ truncatedReason: TruncatedReason | null;
215
+ createdAt: Date | null;
216
+ finishedAt: Date | null;
217
+ /** True when `status === "done"`. */
218
+ readonly done: boolean;
219
+ }
220
+
221
+ export function crawlJobFromResponse(body: Record<string, unknown>): CrawlJob {
222
+ const rawResults = Array.isArray(body.results) ? body.results : [];
223
+ const status =
224
+ typeof body.status === "string" ? (body.status as JobStatus) : "queued";
225
+ const jobId = typeof body.job_id === "string" ? body.job_id : "";
226
+ const total = typeof body.total === "number" ? body.total : 0;
227
+ const completed = typeof body.completed === "number" ? body.completed : 0;
228
+ const createdAt = parseDate(body.created_at);
229
+ const finishedAt = parseDate(body.finished_at);
230
+ const truncated = body.truncated === true;
231
+ const truncatedReason =
232
+ typeof body.truncated_reason === "string"
233
+ ? (body.truncated_reason as TruncatedReason)
234
+ : null;
235
+ const results = rawResults
236
+ .filter((r): r is Record<string, unknown> => r !== null && typeof r === "object")
237
+ .map(crawlItemFromDict);
238
+ return {
239
+ kind: "crawl",
240
+ jobId,
241
+ status,
242
+ total,
243
+ completed,
244
+ results,
245
+ truncated,
246
+ truncatedReason,
247
+ createdAt,
248
+ finishedAt,
249
+ get done(): boolean {
250
+ return status === "done";
251
+ },
252
+ };
253
+ }
254
+
255
+ // ── Usage ────────────────────────────────────────────────────────────────────
256
+
257
+ /**
258
+ * Result of `GET /usage` — current-period quota state.
259
+ *
260
+ * This is the source of truth for rate-limit / quota information. The SDK
261
+ * does not surface `X-RateLimit-*` headers on extract/bulk responses;
262
+ * call `WellMarked.getUsage()` instead.
263
+ */
264
+ export interface Usage {
265
+ plan: string;
266
+ period: string;
267
+ used: number;
268
+ limit: number;
269
+ remaining: number;
270
+ }
271
+
272
+ export function usageFromResponse(body: Record<string, unknown>): Usage {
273
+ return {
274
+ plan: typeof body.plan === "string" ? body.plan : "",
275
+ period: typeof body.period === "string" ? body.period : "",
276
+ used: typeof body.used === "number" ? body.used : 0,
277
+ limit: typeof body.limit === "number" ? body.limit : 0,
278
+ remaining: typeof body.remaining === "number" ? body.remaining : 0,
279
+ };
280
+ }
281
+
282
+ // ── Key rotation ─────────────────────────────────────────────────────────────
283
+
284
+ /**
285
+ * Result of `POST /keys/rotate`.
286
+ *
287
+ * `apiKey` is the new raw key — store it before discarding this object,
288
+ * there is no recovery flow. The previous key is invalidated the moment
289
+ * the rotation call returns 200.
290
+ */
291
+ export interface RotatedKey {
292
+ apiKey: string;
293
+ rotatedAt: Date | null;
294
+ }
295
+
296
+ export function rotatedKeyFromResponse(body: Record<string, unknown>): RotatedKey {
297
+ return {
298
+ apiKey: typeof body.api_key === "string" ? body.api_key : "",
299
+ rotatedAt: parseDate(body.rotated_at),
300
+ };
301
+ }
302
+
303
+ // ── Type guards ──────────────────────────────────────────────────────────────
304
+
305
+ export function isBulkJob(job: BulkJob | CrawlJob): job is BulkJob {
306
+ return job.kind === "bulk";
307
+ }
308
+
309
+ export function isCrawlJob(job: BulkJob | CrawlJob): job is CrawlJob {
310
+ return job.kind === "crawl";
311
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = "1.0.0";