orakle 0.0.2 → 0.1.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/README.md CHANGED
@@ -1,3 +1,149 @@
1
1
  # orakle
2
2
 
3
- Under development client SDK for [orakle.xyz](https://orakle.xyz).
3
+ TypeScript SDK for [orakle.xyz](https://orakle.xyz) — AI-powered pricing intelligence.
4
+
5
+ Submits pricing jobs and streams results over SSE. No polling required. Works in Node.js 18+ and modern browsers.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install orakle
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { OrakleClient } from "orakle";
17
+
18
+ const client = new OrakleClient({ apiKey: "orakle_..." });
19
+
20
+ // Natural language query
21
+ const result = await client.priceCheck({
22
+ request_type: "nl",
23
+ query: "iPhone 15 Pro 256GB unlocked mint condition",
24
+ });
25
+
26
+ console.log(result.fused_distribution.quantiles);
27
+ // { "0.1": 780, "0.25": 820, "0.5": 870, "0.75": 920, "0.9": 960 }
28
+ ```
29
+
30
+ ## Structured Query
31
+
32
+ ```ts
33
+ const result = await client.priceCheck({
34
+ request_type: "structured",
35
+ product: {
36
+ category: "smartphone",
37
+ make: "Apple",
38
+ model: "iPhone 15 Pro",
39
+ storage: "256GB",
40
+ condition: "mint",
41
+ carrier_lock: "unlocked",
42
+ },
43
+ geography: "US",
44
+ });
45
+ ```
46
+
47
+ ## Streaming Progress
48
+
49
+ Track pipeline stages as they happen:
50
+
51
+ ```ts
52
+ // Callback-based
53
+ const result = await client.priceCheck(
54
+ { request_type: "nl", query: "MacBook Air M3 16GB" },
55
+ {
56
+ onProgress: (event) => {
57
+ console.log(`[${event.stage}] ${event.message}`);
58
+ // [analyzing] Parsing product attributes...
59
+ // [price_lookup] Fetching market data...
60
+ // [planning] Generating scenario forecasts...
61
+ // [dispatching] Running pricing agents...
62
+ // [fusing] Combining modality outputs...
63
+ },
64
+ },
65
+ );
66
+
67
+ // Iterator-based — full control over each event
68
+ for await (const event of client.priceCheckStream({
69
+ request_type: "nl",
70
+ query: "PS5 Disc Edition",
71
+ })) {
72
+ if (event.event_type === "stage") {
73
+ console.log(event.stage, event.message);
74
+ }
75
+ if (event.event_type === "completed") {
76
+ console.log("Result:", event.result);
77
+ }
78
+ if (event.event_type === "failed") {
79
+ console.error("Failed:", event.error);
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ```ts
87
+ const client = new OrakleClient({
88
+ apiKey: "orakle_...", // Required
89
+ timeout: 300_000, // Default: 180_000 (3 min), minimum: 180_000
90
+ });
91
+ ```
92
+
93
+ Per-request options:
94
+
95
+ ```ts
96
+ const controller = new AbortController();
97
+
98
+ const result = await client.priceCheck(
99
+ { request_type: "nl", query: "..." },
100
+ {
101
+ timeout: 30_000, // Override client timeout
102
+ signal: controller.signal, // AbortController support
103
+ onProgress: (e) => {}, // Stage progress callback
104
+ },
105
+ );
106
+ ```
107
+
108
+ ## Error Handling
109
+
110
+ ```ts
111
+ import {
112
+ OrakleApiError,
113
+ OrakleTimeoutError,
114
+ OrakleConnectionError,
115
+ } from "orakle";
116
+
117
+ try {
118
+ await client.priceCheck({ request_type: "nl", query: "..." });
119
+ } catch (err) {
120
+ if (err instanceof OrakleApiError) {
121
+ console.error(err.code, err.status, err.message);
122
+ // "rate_limited", 429, "Rate limit exceeded"
123
+ }
124
+ if (err instanceof OrakleTimeoutError) {
125
+ console.error(`Timed out after ${err.timeoutMs}ms`);
126
+ }
127
+ if (err instanceof OrakleConnectionError) {
128
+ console.error("Network error:", err.message);
129
+ }
130
+ }
131
+ ```
132
+
133
+ Error codes: `invalid_request`, `auth_required`, `rate_limited`, `job_not_found`, `insufficient_data`, `internal_error`, `service_unavailable`.
134
+
135
+ ## Result Shape
136
+
137
+ ```ts
138
+ interface PriceCheckResult {
139
+ product_profile: ProductProfile;
140
+ fused_distribution: PriceDistribution;
141
+ scenario_forecast?: ScenarioForecast | null;
142
+ }
143
+ ```
144
+
145
+ `fused_distribution.quantiles` contains percentile price points (p10, p25, p50, p75, p90). `scenario_forecast` includes probability-weighted price scenarios when available.
146
+
147
+ ## License
148
+
149
+ Apache-2.0
package/dist/index.cjs CHANGED
@@ -24,7 +24,9 @@ __export(index_exports, {
24
24
  OrakleClient: () => OrakleClient,
25
25
  OrakleConnectionError: () => OrakleConnectionError,
26
26
  OrakleError: () => OrakleError,
27
- OrakleTimeoutError: () => OrakleTimeoutError
27
+ OrakleTimeoutError: () => OrakleTimeoutError,
28
+ SSEHttpError: () => SSEHttpError,
29
+ createSSEStream: () => createSSEStream
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
30
32
 
@@ -108,7 +110,9 @@ var parseSSEBody = async function* (body, signal) {
108
110
  let currentData = [];
109
111
  const readOrAbort = () => {
110
112
  if (signal?.aborted) {
111
- return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
113
+ return Promise.reject(
114
+ signal.reason ?? new DOMException("Aborted", "AbortError")
115
+ );
112
116
  }
113
117
  return new Promise((resolve, reject) => {
114
118
  const onAbort = () => {
@@ -236,14 +240,18 @@ var SSEHttpError = class extends Error {
236
240
  // src/client.ts
237
241
  var DEFAULT_BASE_URL = "https://api.orakle.xyz";
238
242
  var DEFAULT_TIMEOUT_MS = 18e4;
243
+ var MIN_TIMEOUT_MS = 18e4;
239
244
  var OrakleClient = class {
240
245
  apiKey;
241
- baseUrl;
242
246
  timeout;
247
+ baseUrl;
243
248
  constructor(config) {
244
249
  this.apiKey = config.apiKey;
250
+ this.timeout = Math.max(
251
+ config.timeout ?? DEFAULT_TIMEOUT_MS,
252
+ MIN_TIMEOUT_MS
253
+ );
245
254
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
246
- this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
247
255
  }
248
256
  /**
249
257
  * Submit a pricing job and wait for the result.
@@ -251,7 +259,10 @@ var OrakleClient = class {
251
259
  * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.
252
260
  */
253
261
  async priceCheck(params, options) {
254
- const timeoutMs = options?.timeout ?? this.timeout;
262
+ const timeoutMs = Math.max(
263
+ options?.timeout ?? this.timeout,
264
+ MIN_TIMEOUT_MS
265
+ );
255
266
  const controller = new AbortController();
256
267
  const externalSignal = options?.signal;
257
268
  const onExternalAbort = () => controller.abort();
@@ -274,13 +285,18 @@ var OrakleClient = class {
274
285
  case "stage":
275
286
  options?.onProgress?.(event);
276
287
  break;
288
+ case "partial":
289
+ options?.onPartial?.(event);
290
+ break;
277
291
  case "completed":
278
292
  return event.result;
279
293
  case "failed":
280
294
  throw new OrakleError(event.error);
281
295
  }
282
296
  }
283
- throw new OrakleError("SSE stream ended without a completed or failed event");
297
+ throw new OrakleError(
298
+ "SSE stream ended without a completed or failed event"
299
+ );
284
300
  } catch (err) {
285
301
  throw this.normalizeError(err);
286
302
  } finally {
@@ -294,7 +310,10 @@ var OrakleClient = class {
294
310
  * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.
295
311
  */
296
312
  async *priceCheckStream(params, options) {
297
- const timeoutMs = options?.timeout ?? this.timeout;
313
+ const timeoutMs = Math.max(
314
+ options?.timeout ?? this.timeout,
315
+ MIN_TIMEOUT_MS
316
+ );
298
317
  const controller = new AbortController();
299
318
  const externalSignal = options?.signal;
300
319
  const onExternalAbort = () => controller.abort();
@@ -315,6 +334,8 @@ var OrakleClient = class {
315
334
  if (!event) continue;
316
335
  if (event.event_type === "stage") {
317
336
  options?.onProgress?.(event);
337
+ } else if (event.event_type === "partial") {
338
+ options?.onPartial?.(event);
318
339
  }
319
340
  yield event;
320
341
  if (event.event_type === "completed" || event.event_type === "failed") {
@@ -347,10 +368,7 @@ var OrakleClient = class {
347
368
  if (err instanceof DOMException && err.name === "AbortError") {
348
369
  throw err;
349
370
  }
350
- throw new OrakleConnectionError(
351
- "Failed to connect to Orakle API",
352
- err
353
- );
371
+ throw new OrakleConnectionError("Failed to connect to Orakle API", err);
354
372
  }
355
373
  if (!response.ok) {
356
374
  throw await this.buildApiError(response);
@@ -369,6 +387,8 @@ var OrakleClient = class {
369
387
  switch (eventType) {
370
388
  case "stage":
371
389
  return { ...parsed, event_type: "stage" };
390
+ case "partial":
391
+ return { ...parsed, event_type: "partial" };
372
392
  case "completed":
373
393
  return { ...parsed, event_type: "completed" };
374
394
  case "failed":
@@ -435,6 +455,8 @@ var OrakleClient = class {
435
455
  OrakleClient,
436
456
  OrakleConnectionError,
437
457
  OrakleError,
438
- OrakleTimeoutError
458
+ OrakleTimeoutError,
459
+ SSEHttpError,
460
+ createSSEStream
439
461
  });
440
462
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/sse.ts","../src/client.ts"],"sourcesContent":["// Client\nexport { OrakleClient } from \"./client.js\";\n\n// Errors\nexport {\n OrakleError,\n OrakleApiError,\n OrakleTimeoutError,\n OrakleConnectionError,\n} from \"./errors.js\";\n\n// Types\nexport type {\n OrakleConfig,\n JobStatus,\n PipelineStage,\n ErrorCode,\n Geography,\n ProductProfile,\n DistributionComponent,\n PriceDistribution,\n ScenarioSource,\n Scenario,\n ScenarioForecast,\n NLJobRequest,\n StructuredJobRequest,\n JobRequest,\n PriceCheckResult,\n JobSubmitResponse,\n StageEvent,\n CompletedEvent,\n FailedEvent,\n JobEvent,\n PriceCheckOptions,\n ApiErrorBody,\n} from \"./types.js\";\n","import type { ErrorCode } from \"./types.js\";\n\n/**\n * Base error for all Orakle SDK errors.\n */\nexport class OrakleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrakleError\";\n }\n}\n\n/**\n * HTTP API error with status code and typed error code.\n */\nexport class OrakleApiError extends OrakleError {\n readonly status: number;\n readonly code: ErrorCode;\n\n constructor(message: string, status: number, code: ErrorCode) {\n super(message);\n this.name = \"OrakleApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Timeout error — request or SSE stream exceeded the configured timeout.\n */\nexport class OrakleTimeoutError extends OrakleError {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`);\n this.name = \"OrakleTimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Connection error — network failure, DNS resolution failure, etc.\n */\nexport class OrakleConnectionError extends OrakleError {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"OrakleConnectionError\";\n this.cause = cause;\n }\n}\n","/**\n * SSE parser using fetch + ReadableStream.\n *\n * Cannot use browser EventSource — it doesn't support custom headers\n * (needed for X-Orakle-API-Key). This is the same approach used by\n * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).\n */\n\nimport { OrakleConnectionError } from \"./errors.js\";\nimport type { SSEFrame } from \"./types.js\";\n\ninterface SSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n lastEventId?: string;\n onRetry?: (retryMs: number) => void;\n}\n\n/**\n * Parse a `text/event-stream` response into SSE frames.\n *\n * Yields `{ event, id, data }` for each complete SSE event block.\n * Handles multi-line `data:` fields (joined with newlines).\n * Supports reconnection via `Last-Event-Id` header.\n */\nexport const createSSEStream = async function* (\n options: SSEOptions,\n): AsyncGenerator<SSEFrame> {\n const { url, headers, signal, lastEventId } = options;\n\n const requestHeaders: Record<string, string> = {\n ...headers,\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n };\n\n if (lastEventId) {\n requestHeaders[\"Last-Event-Id\"] = lastEventId;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: requestHeaders,\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n `Failed to connect to SSE stream: ${url}`,\n err,\n );\n }\n\n if (!response.ok) {\n // Let the caller handle HTTP error responses\n const body = await response.text();\n throw new SSEHttpError(response.status, body);\n }\n\n const body = response.body;\n if (!body) {\n throw new OrakleConnectionError(\"Response body is null\");\n }\n\n yield* parseSSEBody(body, signal);\n};\n\n/**\n * Internal: parse a ReadableStream<Uint8Array> as SSE.\n */\nconst parseSSEBody = async function* (\n body: ReadableStream<Uint8Array>,\n signal?: AbortSignal,\n): AsyncGenerator<SSEFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let buffer = \"\";\n let currentEvent = \"\";\n let currentId = \"\";\n let currentData: string[] = [];\n\n type ReadResult = Awaited<ReturnType<typeof reader.read>>;\n\n /** Race reader.read() against the abort signal. */\n const readOrAbort = (): Promise<ReadResult> => {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n }\n return new Promise<ReadResult>((resolve, reject) => {\n const onAbort = () => {\n reject(signal?.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n reader.read().then(\n (result) => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (err: unknown) => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(err);\n },\n );\n });\n };\n\n try {\n while (true) {\n if (signal?.aborted) {\n break;\n }\n\n const { done, value } = await readOrAbort();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line === \"\") {\n // Empty line = end of event block\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n // Reset for next event\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n\n // SSE comment — ignore (keepalive)\n if (line.startsWith(\":\")) {\n continue;\n }\n\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) {\n // Field with no value\n continue;\n }\n\n const field = line.slice(0, colonIdx);\n // Value starts after \": \" (space after colon is optional per spec)\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) {\n value_ = value_.slice(1);\n }\n\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n case \"retry\":\n // Optional retry field — notify caller\n break;\n }\n }\n }\n\n // Flush any remaining event in buffer\n if (buffer !== \"\") {\n const lines = buffer.split(\"\\n\");\n for (const line of lines) {\n if (line === \"\" && currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n if (line.startsWith(\":\") || line === \"\") continue;\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const field = line.slice(0, colonIdx);\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) value_ = value_.slice(1);\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n }\n }\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n }\n } finally {\n reader.releaseLock();\n }\n};\n\n/**\n * Internal error used to communicate HTTP status from the SSE fetch.\n * The client layer catches this and maps it to OrakleApiError.\n */\nexport class SSEHttpError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`SSE request failed with status ${status}`);\n this.name = \"SSEHttpError\";\n this.status = status;\n this.body = body;\n }\n}\n","/**\n * OrakleClient — main entry point for the Orakle TypeScript SDK.\n *\n * Usage:\n * const client = new OrakleClient({ apiKey: \"...\" });\n * const result = await client.priceCheck({ query: \"iPhone 15 Pro 256GB\" });\n */\n\nimport {\n OrakleApiError,\n OrakleConnectionError,\n OrakleError,\n OrakleTimeoutError,\n} from \"./errors.js\";\nimport { SSEHttpError, createSSEStream } from \"./sse.js\";\nimport type {\n ApiErrorBody,\n CompletedEvent,\n ErrorCode,\n FailedEvent,\n JobEvent,\n JobRequest,\n JobSubmitResponse,\n OrakleConfig,\n PriceCheckOptions,\n PriceCheckResult,\n StageEvent,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.orakle.xyz\";\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nexport class OrakleClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: OrakleConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Submit a pricing job and wait for the result.\n *\n * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.\n */\n async priceCheck(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): Promise<PriceCheckResult> {\n const timeoutMs = options?.timeout ?? this.timeout;\n const controller = new AbortController();\n\n // Link external signal to our internal controller\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n switch (event.event_type) {\n case \"stage\":\n options?.onProgress?.(event);\n break;\n case \"completed\":\n return event.result;\n case \"failed\":\n throw new OrakleError(event.error);\n }\n }\n\n // Stream ended without a terminal event\n throw new OrakleError(\"SSE stream ended without a completed or failed event\");\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n /**\n * Submit a pricing job and return an async iterable of all events.\n *\n * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.\n */\n async *priceCheckStream(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): AsyncGenerator<JobEvent> {\n const timeoutMs = options?.timeout ?? this.timeout;\n const controller = new AbortController();\n\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n if (event.event_type === \"stage\") {\n options?.onProgress?.(event);\n }\n\n yield event;\n\n // Terminal events end the stream\n if (event.event_type === \"completed\" || event.event_type === \"failed\") {\n return;\n }\n }\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private async submitJob(\n params: JobRequest,\n signal: AbortSignal,\n ): Promise<JobSubmitResponse> {\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}/v1/jobs`, {\n method: \"POST\",\n headers: {\n ...this.defaultHeaders(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(params),\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n \"Failed to connect to Orakle API\",\n err,\n );\n }\n\n if (!response.ok) {\n throw await this.buildApiError(response);\n }\n\n return (await response.json()) as JobSubmitResponse;\n }\n\n private defaultHeaders(): Record<string, string> {\n return {\n \"X-Orakle-API-Key\": this.apiKey,\n };\n }\n\n private parseJobEvent(eventType: string, data: string): JobEvent | undefined {\n try {\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n switch (eventType) {\n case \"stage\":\n return { ...parsed, event_type: \"stage\" } as StageEvent;\n case \"completed\":\n return { ...parsed, event_type: \"completed\" } as CompletedEvent;\n case \"failed\":\n return { ...parsed, event_type: \"failed\" } as FailedEvent;\n default:\n return undefined;\n }\n } catch {\n return undefined;\n }\n }\n\n private async buildApiError(response: Response): Promise<OrakleApiError> {\n let body: ApiErrorBody | undefined;\n try {\n body = (await response.json()) as ApiErrorBody;\n } catch {\n // Response body is not JSON\n }\n\n const code: ErrorCode = body?.code ?? this.httpStatusToErrorCode(response.status);\n const message = body?.message ?? `API request failed with status ${response.status}`;\n\n return new OrakleApiError(message, response.status, code);\n }\n\n private httpStatusToErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return \"invalid_request\";\n case 401:\n return \"auth_required\";\n case 429:\n return \"rate_limited\";\n case 404:\n return \"job_not_found\";\n case 503:\n return \"service_unavailable\";\n default:\n return \"internal_error\";\n }\n }\n\n private normalizeError(err: unknown): Error {\n if (err instanceof OrakleError) {\n return err;\n }\n\n if (err instanceof SSEHttpError) {\n let body: ApiErrorBody | undefined;\n try {\n body = JSON.parse(err.body) as ApiErrorBody;\n } catch {\n // Not JSON\n }\n const code = body?.code ?? this.httpStatusToErrorCode(err.status);\n const message = body?.message ?? `API request failed with status ${err.status}`;\n return new OrakleApiError(message, err.status, code);\n }\n\n // AbortError from timeout\n if (err instanceof DOMException && err.name === \"AbortError\") {\n // Check if our timeout caused the abort\n return new OrakleTimeoutError(this.timeout);\n }\n\n if (err instanceof Error) {\n return new OrakleConnectionError(err.message, err);\n }\n\n return new OrakleConnectionError(String(err));\n }\n}\n"],"mappings":"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","names":["body"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/sse.ts","../src/client.ts"],"sourcesContent":["// Client\nexport { OrakleClient } from \"./client.js\";\n// Errors\nexport {\n OrakleApiError,\n OrakleConnectionError,\n OrakleError,\n OrakleTimeoutError,\n} from \"./errors.js\";\n// SSE primitives — exposed so server-side proxies (e.g. a Next.js route\n// handler that re-emits a sanitized event stream to the browser) can\n// consume the same parser the client uses, without re-implementing it.\nexport { createSSEStream, SSEHttpError } from \"./sse.js\";\n\n// Types\nexport type {\n ApiErrorBody,\n AttributeCorrection,\n CompletedEvent,\n DistributionComponent,\n ErrorCode,\n FailedEvent,\n ForecastPoint,\n Geography,\n JobEvent,\n JobRequest,\n JobStatus,\n JobSubmitResponse,\n NLJobRequest,\n OrakleConfig,\n PartialEvent,\n PipelineStage,\n PriceCheckOptions,\n PriceCheckResult,\n PriceDistribution,\n ProductProfile,\n Scenario,\n ScenarioForecast,\n ScenarioSource,\n SSEFrame,\n StageEvent,\n StructuredJobRequest,\n ValueDriver,\n} from \"./types.js\";\n","import type { ErrorCode } from \"./types.js\";\n\n/**\n * Base error for all Orakle SDK errors.\n */\nexport class OrakleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrakleError\";\n }\n}\n\n/**\n * HTTP API error with status code and typed error code.\n */\nexport class OrakleApiError extends OrakleError {\n readonly status: number;\n readonly code: ErrorCode;\n\n constructor(message: string, status: number, code: ErrorCode) {\n super(message);\n this.name = \"OrakleApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Timeout error — request or SSE stream exceeded the configured timeout.\n */\nexport class OrakleTimeoutError extends OrakleError {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`);\n this.name = \"OrakleTimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Connection error — network failure, DNS resolution failure, etc.\n */\nexport class OrakleConnectionError extends OrakleError {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"OrakleConnectionError\";\n this.cause = cause;\n }\n}\n","/**\n * SSE parser using fetch + ReadableStream.\n *\n * Cannot use browser EventSource — it doesn't support custom headers\n * (needed for X-Orakle-API-Key). This is the same approach used by\n * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).\n */\n\nimport { OrakleConnectionError } from \"./errors.js\";\nimport type { SSEFrame } from \"./types.js\";\n\ninterface SSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n lastEventId?: string;\n onRetry?: (retryMs: number) => void;\n}\n\n/**\n * Parse a `text/event-stream` response into SSE frames.\n *\n * Yields `{ event, id, data }` for each complete SSE event block.\n * Handles multi-line `data:` fields (joined with newlines).\n * Supports reconnection via `Last-Event-Id` header.\n */\nexport const createSSEStream = async function* (\n options: SSEOptions,\n): AsyncGenerator<SSEFrame> {\n const { url, headers, signal, lastEventId } = options;\n\n const requestHeaders: Record<string, string> = {\n ...headers,\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n };\n\n if (lastEventId) {\n requestHeaders[\"Last-Event-Id\"] = lastEventId;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: requestHeaders,\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n `Failed to connect to SSE stream: ${url}`,\n err,\n );\n }\n\n if (!response.ok) {\n // Let the caller handle HTTP error responses\n const body = await response.text();\n throw new SSEHttpError(response.status, body);\n }\n\n const body = response.body;\n if (!body) {\n throw new OrakleConnectionError(\"Response body is null\");\n }\n\n yield* parseSSEBody(body, signal);\n};\n\n/**\n * Internal: parse a ReadableStream<Uint8Array> as SSE.\n */\nconst parseSSEBody = async function* (\n body: ReadableStream<Uint8Array>,\n signal?: AbortSignal,\n): AsyncGenerator<SSEFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let buffer = \"\";\n let currentEvent = \"\";\n let currentId = \"\";\n let currentData: string[] = [];\n\n type ReadResult = Awaited<ReturnType<typeof reader.read>>;\n\n /** Race reader.read() against the abort signal. */\n const readOrAbort = (): Promise<ReadResult> => {\n if (signal?.aborted) {\n return Promise.reject(\n signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"),\n );\n }\n return new Promise<ReadResult>((resolve, reject) => {\n const onAbort = () => {\n reject(signal?.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n reader.read().then(\n (result) => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (err: unknown) => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(err);\n },\n );\n });\n };\n\n try {\n while (true) {\n if (signal?.aborted) {\n break;\n }\n\n const { done, value } = await readOrAbort();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line === \"\") {\n // Empty line = end of event block\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n // Reset for next event\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n\n // SSE comment — ignore (keepalive)\n if (line.startsWith(\":\")) {\n continue;\n }\n\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) {\n // Field with no value\n continue;\n }\n\n const field = line.slice(0, colonIdx);\n // Value starts after \": \" (space after colon is optional per spec)\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) {\n value_ = value_.slice(1);\n }\n\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n case \"retry\":\n // Optional retry field — notify caller\n break;\n }\n }\n }\n\n // Flush any remaining event in buffer\n if (buffer !== \"\") {\n const lines = buffer.split(\"\\n\");\n for (const line of lines) {\n if (line === \"\" && currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n if (line.startsWith(\":\") || line === \"\") continue;\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const field = line.slice(0, colonIdx);\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) value_ = value_.slice(1);\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n }\n }\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n }\n } finally {\n reader.releaseLock();\n }\n};\n\n/**\n * Internal error used to communicate HTTP status from the SSE fetch.\n * The client layer catches this and maps it to OrakleApiError.\n */\nexport class SSEHttpError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`SSE request failed with status ${status}`);\n this.name = \"SSEHttpError\";\n this.status = status;\n this.body = body;\n }\n}\n","/**\n * OrakleClient — main entry point for the Orakle TypeScript SDK.\n *\n * Usage:\n * const client = new OrakleClient({ apiKey: \"...\" });\n * const result = await client.priceCheck({ query: \"iPhone 15 Pro 256GB\" });\n */\n\nimport {\n OrakleApiError,\n OrakleConnectionError,\n OrakleError,\n OrakleTimeoutError,\n} from \"./errors.js\";\nimport { createSSEStream, SSEHttpError } from \"./sse.js\";\nimport type {\n ApiErrorBody,\n CompletedEvent,\n ErrorCode,\n FailedEvent,\n JobEvent,\n JobRequest,\n JobSubmitResponse,\n OrakleConfig,\n PartialEvent,\n PriceCheckOptions,\n PriceCheckResult,\n StageEvent,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.orakle.xyz\";\nconst DEFAULT_TIMEOUT_MS = 180_000;\nconst MIN_TIMEOUT_MS = 180_000;\n\nexport class OrakleClient {\n private readonly apiKey: string;\n private readonly timeout: number;\n private readonly baseUrl: string;\n\n constructor(config: OrakleConfig) {\n this.apiKey = config.apiKey;\n this.timeout = Math.max(\n config.timeout ?? DEFAULT_TIMEOUT_MS,\n MIN_TIMEOUT_MS,\n );\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n }\n\n /**\n * Submit a pricing job and wait for the result.\n *\n * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.\n */\n async priceCheck(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): Promise<PriceCheckResult> {\n const timeoutMs = Math.max(\n options?.timeout ?? this.timeout,\n MIN_TIMEOUT_MS,\n );\n const controller = new AbortController();\n\n // Link external signal to our internal controller\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n switch (event.event_type) {\n case \"stage\":\n options?.onProgress?.(event);\n break;\n case \"partial\":\n options?.onPartial?.(event);\n break;\n case \"completed\":\n return event.result;\n case \"failed\":\n throw new OrakleError(event.error);\n }\n }\n\n // Stream ended without a terminal event\n throw new OrakleError(\n \"SSE stream ended without a completed or failed event\",\n );\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n /**\n * Submit a pricing job and return an async iterable of all events.\n *\n * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.\n */\n async *priceCheckStream(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): AsyncGenerator<JobEvent> {\n const timeoutMs = Math.max(\n options?.timeout ?? this.timeout,\n MIN_TIMEOUT_MS,\n );\n const controller = new AbortController();\n\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n if (event.event_type === \"stage\") {\n options?.onProgress?.(event);\n } else if (event.event_type === \"partial\") {\n options?.onPartial?.(event);\n }\n\n yield event;\n\n // Terminal events end the stream\n if (event.event_type === \"completed\" || event.event_type === \"failed\") {\n return;\n }\n }\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private async submitJob(\n params: JobRequest,\n signal: AbortSignal,\n ): Promise<JobSubmitResponse> {\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}/v1/jobs`, {\n method: \"POST\",\n headers: {\n ...this.defaultHeaders(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(params),\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\"Failed to connect to Orakle API\", err);\n }\n\n if (!response.ok) {\n throw await this.buildApiError(response);\n }\n\n return (await response.json()) as JobSubmitResponse;\n }\n\n private defaultHeaders(): Record<string, string> {\n return {\n \"X-Orakle-API-Key\": this.apiKey,\n };\n }\n\n private parseJobEvent(eventType: string, data: string): JobEvent | undefined {\n try {\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n switch (eventType) {\n case \"stage\":\n return { ...parsed, event_type: \"stage\" } as StageEvent;\n case \"partial\":\n return { ...parsed, event_type: \"partial\" } as PartialEvent;\n case \"completed\":\n return { ...parsed, event_type: \"completed\" } as CompletedEvent;\n case \"failed\":\n return { ...parsed, event_type: \"failed\" } as FailedEvent;\n default:\n return undefined;\n }\n } catch {\n return undefined;\n }\n }\n\n private async buildApiError(response: Response): Promise<OrakleApiError> {\n let body: ApiErrorBody | undefined;\n try {\n body = (await response.json()) as ApiErrorBody;\n } catch {\n // Response body is not JSON\n }\n\n const code: ErrorCode =\n body?.code ?? this.httpStatusToErrorCode(response.status);\n const message =\n body?.message ?? `API request failed with status ${response.status}`;\n\n return new OrakleApiError(message, response.status, code);\n }\n\n private httpStatusToErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return \"invalid_request\";\n case 401:\n return \"auth_required\";\n case 429:\n return \"rate_limited\";\n case 404:\n return \"job_not_found\";\n case 503:\n return \"service_unavailable\";\n default:\n return \"internal_error\";\n }\n }\n\n private normalizeError(err: unknown): Error {\n if (err instanceof OrakleError) {\n return err;\n }\n\n if (err instanceof SSEHttpError) {\n let body: ApiErrorBody | undefined;\n try {\n body = JSON.parse(err.body) as ApiErrorBody;\n } catch {\n // Not JSON\n }\n const code = body?.code ?? this.httpStatusToErrorCode(err.status);\n const message =\n body?.message ?? `API request failed with status ${err.status}`;\n return new OrakleApiError(message, err.status, code);\n }\n\n // AbortError from timeout\n if (err instanceof DOMException && err.name === \"AbortError\") {\n // Check if our timeout caused the abort\n return new OrakleTimeoutError(this.timeout);\n }\n\n if (err instanceof Error) {\n return new OrakleConnectionError(err.message, err);\n }\n\n return new OrakleConnectionError(String(err));\n }\n}\n"],"mappings":"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","names":["body"]}
package/dist/index.d.cts CHANGED
@@ -3,25 +3,36 @@
3
3
  */
4
4
  interface OrakleConfig {
5
5
  apiKey: string;
6
- baseUrl?: string;
7
6
  timeout?: number;
7
+ /** Override the API base URL. Defaults to https://api.orakle.xyz. */
8
+ baseUrl?: string;
8
9
  }
9
10
  type JobStatus = "pending" | "processing" | "completed" | "failed";
10
11
  type PipelineStage = "analyzing" | "price_lookup" | "planning" | "dispatching" | "fusing";
11
12
  type ErrorCode = "invalid_request" | "auth_required" | "rate_limited" | "job_not_found" | "insufficient_data" | "internal_error" | "service_unavailable";
12
13
  type Geography = "US" | "GB" | "CA" | "AU" | "AE";
14
+ interface AttributeCorrection {
15
+ key: string;
16
+ original: string;
17
+ corrected: string;
18
+ reason: string;
19
+ }
20
+ interface ValueDriver {
21
+ /** Snake_case attribute key matching an entry in `attributes`. */
22
+ key: string;
23
+ /** Attribute value as it appears in `attributes`. */
24
+ value: string;
25
+ }
13
26
  interface ProductProfile {
14
27
  category: string;
15
28
  make: string;
16
29
  model: string;
17
- storage?: string | null;
18
30
  condition?: string | null;
19
- carrier?: string | null;
20
- carrier_lock?: string | null;
21
- color?: string | null;
31
+ attributes?: Record<string, string>;
32
+ corrections?: AttributeCorrection[];
22
33
  geography?: Geography;
23
34
  region?: string | null;
24
- key_value_drivers?: string[];
35
+ value_drivers?: ValueDriver[];
25
36
  missing_attributes?: string[];
26
37
  }
27
38
  interface DistributionComponent {
@@ -52,6 +63,13 @@ interface ScenarioForecast {
52
63
  base_price: number;
53
64
  weighted_expected_price: number;
54
65
  }
66
+ interface ForecastPoint {
67
+ date: string;
68
+ segment: "historical" | "forecast";
69
+ price: number;
70
+ p10?: number;
71
+ p90?: number;
72
+ }
55
73
  interface NLJobRequest {
56
74
  request_type: "nl";
57
75
  query: string;
@@ -69,11 +87,14 @@ interface PriceCheckResult {
69
87
  product_profile: ProductProfile;
70
88
  fused_distribution: PriceDistribution;
71
89
  scenario_forecast?: ScenarioForecast | null;
90
+ forecast_series?: ForecastPoint[];
91
+ forecast_narrative?: string;
72
92
  }
73
93
  interface JobSubmitResponse {
74
94
  job_id: string;
75
95
  status: JobStatus;
76
96
  poll_url: string;
97
+ stream_url: string;
77
98
  estimated_seconds: number;
78
99
  }
79
100
  interface StageEvent {
@@ -83,6 +104,18 @@ interface StageEvent {
83
104
  message: string;
84
105
  timestamp: string;
85
106
  }
107
+ interface PartialEvent {
108
+ event_type: "partial";
109
+ job_id: string;
110
+ /**
111
+ * Subset of PriceCheckResult that the orchestrator emits before the
112
+ * full job completes. Today this is `{ product_profile }` after the
113
+ * Analyzing stage; future stages may add `fused_distribution` etc.
114
+ */
115
+ result: Partial<PriceCheckResult>;
116
+ message?: string;
117
+ timestamp: string;
118
+ }
86
119
  interface CompletedEvent {
87
120
  event_type: "completed";
88
121
  job_id: string;
@@ -97,11 +130,24 @@ interface FailedEvent {
97
130
  message?: string;
98
131
  timestamp: string;
99
132
  }
100
- type JobEvent = StageEvent | CompletedEvent | FailedEvent;
133
+ type JobEvent = StageEvent | PartialEvent | CompletedEvent | FailedEvent;
134
+ interface SSEFrame {
135
+ event: string;
136
+ id: string;
137
+ data: string;
138
+ }
101
139
  interface PriceCheckOptions {
102
140
  signal?: AbortSignal;
103
141
  timeout?: number;
104
142
  onProgress?: (event: StageEvent) => void;
143
+ /**
144
+ * Fires once with the analyzed product profile (and any other early
145
+ * fields) the moment the Analyzing stage finishes — well before the
146
+ * full result is ready. Use this to populate UI placeholders so users
147
+ * see the parsed query immediately. The shape is a partial
148
+ * PriceCheckResult; today it carries `{ product_profile }`.
149
+ */
150
+ onPartial?: (event: PartialEvent) => void;
105
151
  }
106
152
  interface ApiErrorBody {
107
153
  code: ErrorCode;
@@ -119,8 +165,8 @@ interface ApiErrorBody {
119
165
 
120
166
  declare class OrakleClient {
121
167
  private readonly apiKey;
122
- private readonly baseUrl;
123
168
  private readonly timeout;
169
+ private readonly baseUrl;
124
170
  constructor(config: OrakleConfig);
125
171
  /**
126
172
  * Submit a pricing job and wait for the result.
@@ -171,4 +217,37 @@ declare class OrakleConnectionError extends OrakleError {
171
217
  constructor(message: string, cause?: unknown);
172
218
  }
173
219
 
174
- export { type ApiErrorBody, type CompletedEvent, type DistributionComponent, type ErrorCode, type FailedEvent, type Geography, type JobEvent, type JobRequest, type JobStatus, type JobSubmitResponse, type NLJobRequest, OrakleApiError, OrakleClient, type OrakleConfig, OrakleConnectionError, OrakleError, OrakleTimeoutError, type PipelineStage, type PriceCheckOptions, type PriceCheckResult, type PriceDistribution, type ProductProfile, type Scenario, type ScenarioForecast, type ScenarioSource, type StageEvent, type StructuredJobRequest };
220
+ /**
221
+ * SSE parser using fetch + ReadableStream.
222
+ *
223
+ * Cannot use browser EventSource — it doesn't support custom headers
224
+ * (needed for X-Orakle-API-Key). This is the same approach used by
225
+ * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).
226
+ */
227
+
228
+ interface SSEOptions {
229
+ url: string;
230
+ headers: Record<string, string>;
231
+ signal?: AbortSignal;
232
+ lastEventId?: string;
233
+ onRetry?: (retryMs: number) => void;
234
+ }
235
+ /**
236
+ * Parse a `text/event-stream` response into SSE frames.
237
+ *
238
+ * Yields `{ event, id, data }` for each complete SSE event block.
239
+ * Handles multi-line `data:` fields (joined with newlines).
240
+ * Supports reconnection via `Last-Event-Id` header.
241
+ */
242
+ declare const createSSEStream: (options: SSEOptions) => AsyncGenerator<SSEFrame>;
243
+ /**
244
+ * Internal error used to communicate HTTP status from the SSE fetch.
245
+ * The client layer catches this and maps it to OrakleApiError.
246
+ */
247
+ declare class SSEHttpError extends Error {
248
+ readonly status: number;
249
+ readonly body: string;
250
+ constructor(status: number, body: string);
251
+ }
252
+
253
+ export { type ApiErrorBody, type AttributeCorrection, type CompletedEvent, type DistributionComponent, type ErrorCode, type FailedEvent, type ForecastPoint, type Geography, type JobEvent, type JobRequest, type JobStatus, type JobSubmitResponse, type NLJobRequest, OrakleApiError, OrakleClient, type OrakleConfig, OrakleConnectionError, OrakleError, OrakleTimeoutError, type PartialEvent, type PipelineStage, type PriceCheckOptions, type PriceCheckResult, type PriceDistribution, type ProductProfile, type SSEFrame, SSEHttpError, type Scenario, type ScenarioForecast, type ScenarioSource, type StageEvent, type StructuredJobRequest, type ValueDriver, createSSEStream };
package/dist/index.d.ts CHANGED
@@ -3,25 +3,36 @@
3
3
  */
4
4
  interface OrakleConfig {
5
5
  apiKey: string;
6
- baseUrl?: string;
7
6
  timeout?: number;
7
+ /** Override the API base URL. Defaults to https://api.orakle.xyz. */
8
+ baseUrl?: string;
8
9
  }
9
10
  type JobStatus = "pending" | "processing" | "completed" | "failed";
10
11
  type PipelineStage = "analyzing" | "price_lookup" | "planning" | "dispatching" | "fusing";
11
12
  type ErrorCode = "invalid_request" | "auth_required" | "rate_limited" | "job_not_found" | "insufficient_data" | "internal_error" | "service_unavailable";
12
13
  type Geography = "US" | "GB" | "CA" | "AU" | "AE";
14
+ interface AttributeCorrection {
15
+ key: string;
16
+ original: string;
17
+ corrected: string;
18
+ reason: string;
19
+ }
20
+ interface ValueDriver {
21
+ /** Snake_case attribute key matching an entry in `attributes`. */
22
+ key: string;
23
+ /** Attribute value as it appears in `attributes`. */
24
+ value: string;
25
+ }
13
26
  interface ProductProfile {
14
27
  category: string;
15
28
  make: string;
16
29
  model: string;
17
- storage?: string | null;
18
30
  condition?: string | null;
19
- carrier?: string | null;
20
- carrier_lock?: string | null;
21
- color?: string | null;
31
+ attributes?: Record<string, string>;
32
+ corrections?: AttributeCorrection[];
22
33
  geography?: Geography;
23
34
  region?: string | null;
24
- key_value_drivers?: string[];
35
+ value_drivers?: ValueDriver[];
25
36
  missing_attributes?: string[];
26
37
  }
27
38
  interface DistributionComponent {
@@ -52,6 +63,13 @@ interface ScenarioForecast {
52
63
  base_price: number;
53
64
  weighted_expected_price: number;
54
65
  }
66
+ interface ForecastPoint {
67
+ date: string;
68
+ segment: "historical" | "forecast";
69
+ price: number;
70
+ p10?: number;
71
+ p90?: number;
72
+ }
55
73
  interface NLJobRequest {
56
74
  request_type: "nl";
57
75
  query: string;
@@ -69,11 +87,14 @@ interface PriceCheckResult {
69
87
  product_profile: ProductProfile;
70
88
  fused_distribution: PriceDistribution;
71
89
  scenario_forecast?: ScenarioForecast | null;
90
+ forecast_series?: ForecastPoint[];
91
+ forecast_narrative?: string;
72
92
  }
73
93
  interface JobSubmitResponse {
74
94
  job_id: string;
75
95
  status: JobStatus;
76
96
  poll_url: string;
97
+ stream_url: string;
77
98
  estimated_seconds: number;
78
99
  }
79
100
  interface StageEvent {
@@ -83,6 +104,18 @@ interface StageEvent {
83
104
  message: string;
84
105
  timestamp: string;
85
106
  }
107
+ interface PartialEvent {
108
+ event_type: "partial";
109
+ job_id: string;
110
+ /**
111
+ * Subset of PriceCheckResult that the orchestrator emits before the
112
+ * full job completes. Today this is `{ product_profile }` after the
113
+ * Analyzing stage; future stages may add `fused_distribution` etc.
114
+ */
115
+ result: Partial<PriceCheckResult>;
116
+ message?: string;
117
+ timestamp: string;
118
+ }
86
119
  interface CompletedEvent {
87
120
  event_type: "completed";
88
121
  job_id: string;
@@ -97,11 +130,24 @@ interface FailedEvent {
97
130
  message?: string;
98
131
  timestamp: string;
99
132
  }
100
- type JobEvent = StageEvent | CompletedEvent | FailedEvent;
133
+ type JobEvent = StageEvent | PartialEvent | CompletedEvent | FailedEvent;
134
+ interface SSEFrame {
135
+ event: string;
136
+ id: string;
137
+ data: string;
138
+ }
101
139
  interface PriceCheckOptions {
102
140
  signal?: AbortSignal;
103
141
  timeout?: number;
104
142
  onProgress?: (event: StageEvent) => void;
143
+ /**
144
+ * Fires once with the analyzed product profile (and any other early
145
+ * fields) the moment the Analyzing stage finishes — well before the
146
+ * full result is ready. Use this to populate UI placeholders so users
147
+ * see the parsed query immediately. The shape is a partial
148
+ * PriceCheckResult; today it carries `{ product_profile }`.
149
+ */
150
+ onPartial?: (event: PartialEvent) => void;
105
151
  }
106
152
  interface ApiErrorBody {
107
153
  code: ErrorCode;
@@ -119,8 +165,8 @@ interface ApiErrorBody {
119
165
 
120
166
  declare class OrakleClient {
121
167
  private readonly apiKey;
122
- private readonly baseUrl;
123
168
  private readonly timeout;
169
+ private readonly baseUrl;
124
170
  constructor(config: OrakleConfig);
125
171
  /**
126
172
  * Submit a pricing job and wait for the result.
@@ -171,4 +217,37 @@ declare class OrakleConnectionError extends OrakleError {
171
217
  constructor(message: string, cause?: unknown);
172
218
  }
173
219
 
174
- export { type ApiErrorBody, type CompletedEvent, type DistributionComponent, type ErrorCode, type FailedEvent, type Geography, type JobEvent, type JobRequest, type JobStatus, type JobSubmitResponse, type NLJobRequest, OrakleApiError, OrakleClient, type OrakleConfig, OrakleConnectionError, OrakleError, OrakleTimeoutError, type PipelineStage, type PriceCheckOptions, type PriceCheckResult, type PriceDistribution, type ProductProfile, type Scenario, type ScenarioForecast, type ScenarioSource, type StageEvent, type StructuredJobRequest };
220
+ /**
221
+ * SSE parser using fetch + ReadableStream.
222
+ *
223
+ * Cannot use browser EventSource — it doesn't support custom headers
224
+ * (needed for X-Orakle-API-Key). This is the same approach used by
225
+ * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).
226
+ */
227
+
228
+ interface SSEOptions {
229
+ url: string;
230
+ headers: Record<string, string>;
231
+ signal?: AbortSignal;
232
+ lastEventId?: string;
233
+ onRetry?: (retryMs: number) => void;
234
+ }
235
+ /**
236
+ * Parse a `text/event-stream` response into SSE frames.
237
+ *
238
+ * Yields `{ event, id, data }` for each complete SSE event block.
239
+ * Handles multi-line `data:` fields (joined with newlines).
240
+ * Supports reconnection via `Last-Event-Id` header.
241
+ */
242
+ declare const createSSEStream: (options: SSEOptions) => AsyncGenerator<SSEFrame>;
243
+ /**
244
+ * Internal error used to communicate HTTP status from the SSE fetch.
245
+ * The client layer catches this and maps it to OrakleApiError.
246
+ */
247
+ declare class SSEHttpError extends Error {
248
+ readonly status: number;
249
+ readonly body: string;
250
+ constructor(status: number, body: string);
251
+ }
252
+
253
+ export { type ApiErrorBody, type AttributeCorrection, type CompletedEvent, type DistributionComponent, type ErrorCode, type FailedEvent, type ForecastPoint, type Geography, type JobEvent, type JobRequest, type JobStatus, type JobSubmitResponse, type NLJobRequest, OrakleApiError, OrakleClient, type OrakleConfig, OrakleConnectionError, OrakleError, OrakleTimeoutError, type PartialEvent, type PipelineStage, type PriceCheckOptions, type PriceCheckResult, type PriceDistribution, type ProductProfile, type SSEFrame, SSEHttpError, type Scenario, type ScenarioForecast, type ScenarioSource, type StageEvent, type StructuredJobRequest, type ValueDriver, createSSEStream };
package/dist/index.js CHANGED
@@ -78,7 +78,9 @@ var parseSSEBody = async function* (body, signal) {
78
78
  let currentData = [];
79
79
  const readOrAbort = () => {
80
80
  if (signal?.aborted) {
81
- return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
81
+ return Promise.reject(
82
+ signal.reason ?? new DOMException("Aborted", "AbortError")
83
+ );
82
84
  }
83
85
  return new Promise((resolve, reject) => {
84
86
  const onAbort = () => {
@@ -206,14 +208,18 @@ var SSEHttpError = class extends Error {
206
208
  // src/client.ts
207
209
  var DEFAULT_BASE_URL = "https://api.orakle.xyz";
208
210
  var DEFAULT_TIMEOUT_MS = 18e4;
211
+ var MIN_TIMEOUT_MS = 18e4;
209
212
  var OrakleClient = class {
210
213
  apiKey;
211
- baseUrl;
212
214
  timeout;
215
+ baseUrl;
213
216
  constructor(config) {
214
217
  this.apiKey = config.apiKey;
218
+ this.timeout = Math.max(
219
+ config.timeout ?? DEFAULT_TIMEOUT_MS,
220
+ MIN_TIMEOUT_MS
221
+ );
215
222
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
216
- this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
217
223
  }
218
224
  /**
219
225
  * Submit a pricing job and wait for the result.
@@ -221,7 +227,10 @@ var OrakleClient = class {
221
227
  * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.
222
228
  */
223
229
  async priceCheck(params, options) {
224
- const timeoutMs = options?.timeout ?? this.timeout;
230
+ const timeoutMs = Math.max(
231
+ options?.timeout ?? this.timeout,
232
+ MIN_TIMEOUT_MS
233
+ );
225
234
  const controller = new AbortController();
226
235
  const externalSignal = options?.signal;
227
236
  const onExternalAbort = () => controller.abort();
@@ -244,13 +253,18 @@ var OrakleClient = class {
244
253
  case "stage":
245
254
  options?.onProgress?.(event);
246
255
  break;
256
+ case "partial":
257
+ options?.onPartial?.(event);
258
+ break;
247
259
  case "completed":
248
260
  return event.result;
249
261
  case "failed":
250
262
  throw new OrakleError(event.error);
251
263
  }
252
264
  }
253
- throw new OrakleError("SSE stream ended without a completed or failed event");
265
+ throw new OrakleError(
266
+ "SSE stream ended without a completed or failed event"
267
+ );
254
268
  } catch (err) {
255
269
  throw this.normalizeError(err);
256
270
  } finally {
@@ -264,7 +278,10 @@ var OrakleClient = class {
264
278
  * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.
265
279
  */
266
280
  async *priceCheckStream(params, options) {
267
- const timeoutMs = options?.timeout ?? this.timeout;
281
+ const timeoutMs = Math.max(
282
+ options?.timeout ?? this.timeout,
283
+ MIN_TIMEOUT_MS
284
+ );
268
285
  const controller = new AbortController();
269
286
  const externalSignal = options?.signal;
270
287
  const onExternalAbort = () => controller.abort();
@@ -285,6 +302,8 @@ var OrakleClient = class {
285
302
  if (!event) continue;
286
303
  if (event.event_type === "stage") {
287
304
  options?.onProgress?.(event);
305
+ } else if (event.event_type === "partial") {
306
+ options?.onPartial?.(event);
288
307
  }
289
308
  yield event;
290
309
  if (event.event_type === "completed" || event.event_type === "failed") {
@@ -317,10 +336,7 @@ var OrakleClient = class {
317
336
  if (err instanceof DOMException && err.name === "AbortError") {
318
337
  throw err;
319
338
  }
320
- throw new OrakleConnectionError(
321
- "Failed to connect to Orakle API",
322
- err
323
- );
339
+ throw new OrakleConnectionError("Failed to connect to Orakle API", err);
324
340
  }
325
341
  if (!response.ok) {
326
342
  throw await this.buildApiError(response);
@@ -339,6 +355,8 @@ var OrakleClient = class {
339
355
  switch (eventType) {
340
356
  case "stage":
341
357
  return { ...parsed, event_type: "stage" };
358
+ case "partial":
359
+ return { ...parsed, event_type: "partial" };
342
360
  case "completed":
343
361
  return { ...parsed, event_type: "completed" };
344
362
  case "failed":
@@ -404,6 +422,8 @@ export {
404
422
  OrakleClient,
405
423
  OrakleConnectionError,
406
424
  OrakleError,
407
- OrakleTimeoutError
425
+ OrakleTimeoutError,
426
+ SSEHttpError,
427
+ createSSEStream
408
428
  };
409
429
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/sse.ts","../src/client.ts"],"sourcesContent":["import type { ErrorCode } from \"./types.js\";\n\n/**\n * Base error for all Orakle SDK errors.\n */\nexport class OrakleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrakleError\";\n }\n}\n\n/**\n * HTTP API error with status code and typed error code.\n */\nexport class OrakleApiError extends OrakleError {\n readonly status: number;\n readonly code: ErrorCode;\n\n constructor(message: string, status: number, code: ErrorCode) {\n super(message);\n this.name = \"OrakleApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Timeout error — request or SSE stream exceeded the configured timeout.\n */\nexport class OrakleTimeoutError extends OrakleError {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`);\n this.name = \"OrakleTimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Connection error — network failure, DNS resolution failure, etc.\n */\nexport class OrakleConnectionError extends OrakleError {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"OrakleConnectionError\";\n this.cause = cause;\n }\n}\n","/**\n * SSE parser using fetch + ReadableStream.\n *\n * Cannot use browser EventSource — it doesn't support custom headers\n * (needed for X-Orakle-API-Key). This is the same approach used by\n * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).\n */\n\nimport { OrakleConnectionError } from \"./errors.js\";\nimport type { SSEFrame } from \"./types.js\";\n\ninterface SSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n lastEventId?: string;\n onRetry?: (retryMs: number) => void;\n}\n\n/**\n * Parse a `text/event-stream` response into SSE frames.\n *\n * Yields `{ event, id, data }` for each complete SSE event block.\n * Handles multi-line `data:` fields (joined with newlines).\n * Supports reconnection via `Last-Event-Id` header.\n */\nexport const createSSEStream = async function* (\n options: SSEOptions,\n): AsyncGenerator<SSEFrame> {\n const { url, headers, signal, lastEventId } = options;\n\n const requestHeaders: Record<string, string> = {\n ...headers,\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n };\n\n if (lastEventId) {\n requestHeaders[\"Last-Event-Id\"] = lastEventId;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: requestHeaders,\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n `Failed to connect to SSE stream: ${url}`,\n err,\n );\n }\n\n if (!response.ok) {\n // Let the caller handle HTTP error responses\n const body = await response.text();\n throw new SSEHttpError(response.status, body);\n }\n\n const body = response.body;\n if (!body) {\n throw new OrakleConnectionError(\"Response body is null\");\n }\n\n yield* parseSSEBody(body, signal);\n};\n\n/**\n * Internal: parse a ReadableStream<Uint8Array> as SSE.\n */\nconst parseSSEBody = async function* (\n body: ReadableStream<Uint8Array>,\n signal?: AbortSignal,\n): AsyncGenerator<SSEFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let buffer = \"\";\n let currentEvent = \"\";\n let currentId = \"\";\n let currentData: string[] = [];\n\n type ReadResult = Awaited<ReturnType<typeof reader.read>>;\n\n /** Race reader.read() against the abort signal. */\n const readOrAbort = (): Promise<ReadResult> => {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n }\n return new Promise<ReadResult>((resolve, reject) => {\n const onAbort = () => {\n reject(signal?.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n reader.read().then(\n (result) => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (err: unknown) => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(err);\n },\n );\n });\n };\n\n try {\n while (true) {\n if (signal?.aborted) {\n break;\n }\n\n const { done, value } = await readOrAbort();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line === \"\") {\n // Empty line = end of event block\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n // Reset for next event\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n\n // SSE comment — ignore (keepalive)\n if (line.startsWith(\":\")) {\n continue;\n }\n\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) {\n // Field with no value\n continue;\n }\n\n const field = line.slice(0, colonIdx);\n // Value starts after \": \" (space after colon is optional per spec)\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) {\n value_ = value_.slice(1);\n }\n\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n case \"retry\":\n // Optional retry field — notify caller\n break;\n }\n }\n }\n\n // Flush any remaining event in buffer\n if (buffer !== \"\") {\n const lines = buffer.split(\"\\n\");\n for (const line of lines) {\n if (line === \"\" && currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n if (line.startsWith(\":\") || line === \"\") continue;\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const field = line.slice(0, colonIdx);\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) value_ = value_.slice(1);\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n }\n }\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n }\n } finally {\n reader.releaseLock();\n }\n};\n\n/**\n * Internal error used to communicate HTTP status from the SSE fetch.\n * The client layer catches this and maps it to OrakleApiError.\n */\nexport class SSEHttpError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`SSE request failed with status ${status}`);\n this.name = \"SSEHttpError\";\n this.status = status;\n this.body = body;\n }\n}\n","/**\n * OrakleClient — main entry point for the Orakle TypeScript SDK.\n *\n * Usage:\n * const client = new OrakleClient({ apiKey: \"...\" });\n * const result = await client.priceCheck({ query: \"iPhone 15 Pro 256GB\" });\n */\n\nimport {\n OrakleApiError,\n OrakleConnectionError,\n OrakleError,\n OrakleTimeoutError,\n} from \"./errors.js\";\nimport { SSEHttpError, createSSEStream } from \"./sse.js\";\nimport type {\n ApiErrorBody,\n CompletedEvent,\n ErrorCode,\n FailedEvent,\n JobEvent,\n JobRequest,\n JobSubmitResponse,\n OrakleConfig,\n PriceCheckOptions,\n PriceCheckResult,\n StageEvent,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.orakle.xyz\";\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nexport class OrakleClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: OrakleConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Submit a pricing job and wait for the result.\n *\n * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.\n */\n async priceCheck(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): Promise<PriceCheckResult> {\n const timeoutMs = options?.timeout ?? this.timeout;\n const controller = new AbortController();\n\n // Link external signal to our internal controller\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n switch (event.event_type) {\n case \"stage\":\n options?.onProgress?.(event);\n break;\n case \"completed\":\n return event.result;\n case \"failed\":\n throw new OrakleError(event.error);\n }\n }\n\n // Stream ended without a terminal event\n throw new OrakleError(\"SSE stream ended without a completed or failed event\");\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n /**\n * Submit a pricing job and return an async iterable of all events.\n *\n * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.\n */\n async *priceCheckStream(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): AsyncGenerator<JobEvent> {\n const timeoutMs = options?.timeout ?? this.timeout;\n const controller = new AbortController();\n\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n if (event.event_type === \"stage\") {\n options?.onProgress?.(event);\n }\n\n yield event;\n\n // Terminal events end the stream\n if (event.event_type === \"completed\" || event.event_type === \"failed\") {\n return;\n }\n }\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private async submitJob(\n params: JobRequest,\n signal: AbortSignal,\n ): Promise<JobSubmitResponse> {\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}/v1/jobs`, {\n method: \"POST\",\n headers: {\n ...this.defaultHeaders(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(params),\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n \"Failed to connect to Orakle API\",\n err,\n );\n }\n\n if (!response.ok) {\n throw await this.buildApiError(response);\n }\n\n return (await response.json()) as JobSubmitResponse;\n }\n\n private defaultHeaders(): Record<string, string> {\n return {\n \"X-Orakle-API-Key\": this.apiKey,\n };\n }\n\n private parseJobEvent(eventType: string, data: string): JobEvent | undefined {\n try {\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n switch (eventType) {\n case \"stage\":\n return { ...parsed, event_type: \"stage\" } as StageEvent;\n case \"completed\":\n return { ...parsed, event_type: \"completed\" } as CompletedEvent;\n case \"failed\":\n return { ...parsed, event_type: \"failed\" } as FailedEvent;\n default:\n return undefined;\n }\n } catch {\n return undefined;\n }\n }\n\n private async buildApiError(response: Response): Promise<OrakleApiError> {\n let body: ApiErrorBody | undefined;\n try {\n body = (await response.json()) as ApiErrorBody;\n } catch {\n // Response body is not JSON\n }\n\n const code: ErrorCode = body?.code ?? this.httpStatusToErrorCode(response.status);\n const message = body?.message ?? `API request failed with status ${response.status}`;\n\n return new OrakleApiError(message, response.status, code);\n }\n\n private httpStatusToErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return \"invalid_request\";\n case 401:\n return \"auth_required\";\n case 429:\n return \"rate_limited\";\n case 404:\n return \"job_not_found\";\n case 503:\n return \"service_unavailable\";\n default:\n return \"internal_error\";\n }\n }\n\n private normalizeError(err: unknown): Error {\n if (err instanceof OrakleError) {\n return err;\n }\n\n if (err instanceof SSEHttpError) {\n let body: ApiErrorBody | undefined;\n try {\n body = JSON.parse(err.body) as ApiErrorBody;\n } catch {\n // Not JSON\n }\n const code = body?.code ?? this.httpStatusToErrorCode(err.status);\n const message = body?.message ?? `API request failed with status ${err.status}`;\n return new OrakleApiError(message, err.status, code);\n }\n\n // AbortError from timeout\n if (err instanceof DOMException && err.name === \"AbortError\") {\n // Check if our timeout caused the abort\n return new OrakleTimeoutError(this.timeout);\n }\n\n if (err instanceof Error) {\n return new OrakleConnectionError(err.message, err);\n }\n\n return new OrakleConnectionError(String(err));\n }\n}\n"],"mappings":"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","names":["body"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/sse.ts","../src/client.ts"],"sourcesContent":["import type { ErrorCode } from \"./types.js\";\n\n/**\n * Base error for all Orakle SDK errors.\n */\nexport class OrakleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrakleError\";\n }\n}\n\n/**\n * HTTP API error with status code and typed error code.\n */\nexport class OrakleApiError extends OrakleError {\n readonly status: number;\n readonly code: ErrorCode;\n\n constructor(message: string, status: number, code: ErrorCode) {\n super(message);\n this.name = \"OrakleApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Timeout error — request or SSE stream exceeded the configured timeout.\n */\nexport class OrakleTimeoutError extends OrakleError {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`);\n this.name = \"OrakleTimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Connection error — network failure, DNS resolution failure, etc.\n */\nexport class OrakleConnectionError extends OrakleError {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"OrakleConnectionError\";\n this.cause = cause;\n }\n}\n","/**\n * SSE parser using fetch + ReadableStream.\n *\n * Cannot use browser EventSource — it doesn't support custom headers\n * (needed for X-Orakle-API-Key). This is the same approach used by\n * the OpenAI SDK. Works in browsers and Node.js 18+ (native fetch).\n */\n\nimport { OrakleConnectionError } from \"./errors.js\";\nimport type { SSEFrame } from \"./types.js\";\n\ninterface SSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n lastEventId?: string;\n onRetry?: (retryMs: number) => void;\n}\n\n/**\n * Parse a `text/event-stream` response into SSE frames.\n *\n * Yields `{ event, id, data }` for each complete SSE event block.\n * Handles multi-line `data:` fields (joined with newlines).\n * Supports reconnection via `Last-Event-Id` header.\n */\nexport const createSSEStream = async function* (\n options: SSEOptions,\n): AsyncGenerator<SSEFrame> {\n const { url, headers, signal, lastEventId } = options;\n\n const requestHeaders: Record<string, string> = {\n ...headers,\n Accept: \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n };\n\n if (lastEventId) {\n requestHeaders[\"Last-Event-Id\"] = lastEventId;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: requestHeaders,\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\n `Failed to connect to SSE stream: ${url}`,\n err,\n );\n }\n\n if (!response.ok) {\n // Let the caller handle HTTP error responses\n const body = await response.text();\n throw new SSEHttpError(response.status, body);\n }\n\n const body = response.body;\n if (!body) {\n throw new OrakleConnectionError(\"Response body is null\");\n }\n\n yield* parseSSEBody(body, signal);\n};\n\n/**\n * Internal: parse a ReadableStream<Uint8Array> as SSE.\n */\nconst parseSSEBody = async function* (\n body: ReadableStream<Uint8Array>,\n signal?: AbortSignal,\n): AsyncGenerator<SSEFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let buffer = \"\";\n let currentEvent = \"\";\n let currentId = \"\";\n let currentData: string[] = [];\n\n type ReadResult = Awaited<ReturnType<typeof reader.read>>;\n\n /** Race reader.read() against the abort signal. */\n const readOrAbort = (): Promise<ReadResult> => {\n if (signal?.aborted) {\n return Promise.reject(\n signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"),\n );\n }\n return new Promise<ReadResult>((resolve, reject) => {\n const onAbort = () => {\n reject(signal?.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n reader.read().then(\n (result) => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (err: unknown) => {\n signal?.removeEventListener(\"abort\", onAbort);\n reject(err);\n },\n );\n });\n };\n\n try {\n while (true) {\n if (signal?.aborted) {\n break;\n }\n\n const { done, value } = await readOrAbort();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n // Keep the last partial line in the buffer\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line === \"\") {\n // Empty line = end of event block\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n // Reset for next event\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n\n // SSE comment — ignore (keepalive)\n if (line.startsWith(\":\")) {\n continue;\n }\n\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) {\n // Field with no value\n continue;\n }\n\n const field = line.slice(0, colonIdx);\n // Value starts after \": \" (space after colon is optional per spec)\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) {\n value_ = value_.slice(1);\n }\n\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n case \"retry\":\n // Optional retry field — notify caller\n break;\n }\n }\n }\n\n // Flush any remaining event in buffer\n if (buffer !== \"\") {\n const lines = buffer.split(\"\\n\");\n for (const line of lines) {\n if (line === \"\" && currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n currentEvent = \"\";\n currentId = \"\";\n currentData = [];\n continue;\n }\n if (line.startsWith(\":\") || line === \"\") continue;\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const field = line.slice(0, colonIdx);\n let value_ = line.slice(colonIdx + 1);\n if (value_.startsWith(\" \")) value_ = value_.slice(1);\n switch (field) {\n case \"event\":\n currentEvent = value_;\n break;\n case \"id\":\n currentId = value_;\n break;\n case \"data\":\n currentData.push(value_);\n break;\n }\n }\n if (currentData.length > 0) {\n yield {\n event: currentEvent ?? \"message\",\n id: currentId,\n data: currentData.join(\"\\n\"),\n };\n }\n }\n } finally {\n reader.releaseLock();\n }\n};\n\n/**\n * Internal error used to communicate HTTP status from the SSE fetch.\n * The client layer catches this and maps it to OrakleApiError.\n */\nexport class SSEHttpError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`SSE request failed with status ${status}`);\n this.name = \"SSEHttpError\";\n this.status = status;\n this.body = body;\n }\n}\n","/**\n * OrakleClient — main entry point for the Orakle TypeScript SDK.\n *\n * Usage:\n * const client = new OrakleClient({ apiKey: \"...\" });\n * const result = await client.priceCheck({ query: \"iPhone 15 Pro 256GB\" });\n */\n\nimport {\n OrakleApiError,\n OrakleConnectionError,\n OrakleError,\n OrakleTimeoutError,\n} from \"./errors.js\";\nimport { createSSEStream, SSEHttpError } from \"./sse.js\";\nimport type {\n ApiErrorBody,\n CompletedEvent,\n ErrorCode,\n FailedEvent,\n JobEvent,\n JobRequest,\n JobSubmitResponse,\n OrakleConfig,\n PartialEvent,\n PriceCheckOptions,\n PriceCheckResult,\n StageEvent,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.orakle.xyz\";\nconst DEFAULT_TIMEOUT_MS = 180_000;\nconst MIN_TIMEOUT_MS = 180_000;\n\nexport class OrakleClient {\n private readonly apiKey: string;\n private readonly timeout: number;\n private readonly baseUrl: string;\n\n constructor(config: OrakleConfig) {\n this.apiKey = config.apiKey;\n this.timeout = Math.max(\n config.timeout ?? DEFAULT_TIMEOUT_MS,\n MIN_TIMEOUT_MS,\n );\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n }\n\n /**\n * Submit a pricing job and wait for the result.\n *\n * Internally: POST /v1/jobs -> open SSE stream -> resolve on completed, reject on failed.\n */\n async priceCheck(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): Promise<PriceCheckResult> {\n const timeoutMs = Math.max(\n options?.timeout ?? this.timeout,\n MIN_TIMEOUT_MS,\n );\n const controller = new AbortController();\n\n // Link external signal to our internal controller\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n switch (event.event_type) {\n case \"stage\":\n options?.onProgress?.(event);\n break;\n case \"partial\":\n options?.onPartial?.(event);\n break;\n case \"completed\":\n return event.result;\n case \"failed\":\n throw new OrakleError(event.error);\n }\n }\n\n // Stream ended without a terminal event\n throw new OrakleError(\n \"SSE stream ended without a completed or failed event\",\n );\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n /**\n * Submit a pricing job and return an async iterable of all events.\n *\n * Yields StageEvent and CompletedEvent/FailedEvent as they arrive.\n */\n async *priceCheckStream(\n params: JobRequest,\n options?: PriceCheckOptions,\n ): AsyncGenerator<JobEvent> {\n const timeoutMs = Math.max(\n options?.timeout ?? this.timeout,\n MIN_TIMEOUT_MS,\n );\n const controller = new AbortController();\n\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n\n const timer = setTimeout(() => {\n controller.abort(new OrakleTimeoutError(timeoutMs));\n }, timeoutMs);\n\n try {\n const job = await this.submitJob(params, controller.signal);\n const streamUrl = `${this.baseUrl}/v1/jobs/${job.job_id}/stream`;\n\n const stream = createSSEStream({\n url: streamUrl,\n headers: this.defaultHeaders(),\n signal: controller.signal,\n });\n\n for await (const frame of stream) {\n const event = this.parseJobEvent(frame.event, frame.data);\n if (!event) continue;\n\n if (event.event_type === \"stage\") {\n options?.onProgress?.(event);\n } else if (event.event_type === \"partial\") {\n options?.onPartial?.(event);\n }\n\n yield event;\n\n // Terminal events end the stream\n if (event.event_type === \"completed\" || event.event_type === \"failed\") {\n return;\n }\n }\n } catch (err: unknown) {\n throw this.normalizeError(err);\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private async submitJob(\n params: JobRequest,\n signal: AbortSignal,\n ): Promise<JobSubmitResponse> {\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}/v1/jobs`, {\n method: \"POST\",\n headers: {\n ...this.defaultHeaders(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(params),\n signal,\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n throw new OrakleConnectionError(\"Failed to connect to Orakle API\", err);\n }\n\n if (!response.ok) {\n throw await this.buildApiError(response);\n }\n\n return (await response.json()) as JobSubmitResponse;\n }\n\n private defaultHeaders(): Record<string, string> {\n return {\n \"X-Orakle-API-Key\": this.apiKey,\n };\n }\n\n private parseJobEvent(eventType: string, data: string): JobEvent | undefined {\n try {\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n switch (eventType) {\n case \"stage\":\n return { ...parsed, event_type: \"stage\" } as StageEvent;\n case \"partial\":\n return { ...parsed, event_type: \"partial\" } as PartialEvent;\n case \"completed\":\n return { ...parsed, event_type: \"completed\" } as CompletedEvent;\n case \"failed\":\n return { ...parsed, event_type: \"failed\" } as FailedEvent;\n default:\n return undefined;\n }\n } catch {\n return undefined;\n }\n }\n\n private async buildApiError(response: Response): Promise<OrakleApiError> {\n let body: ApiErrorBody | undefined;\n try {\n body = (await response.json()) as ApiErrorBody;\n } catch {\n // Response body is not JSON\n }\n\n const code: ErrorCode =\n body?.code ?? this.httpStatusToErrorCode(response.status);\n const message =\n body?.message ?? `API request failed with status ${response.status}`;\n\n return new OrakleApiError(message, response.status, code);\n }\n\n private httpStatusToErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return \"invalid_request\";\n case 401:\n return \"auth_required\";\n case 429:\n return \"rate_limited\";\n case 404:\n return \"job_not_found\";\n case 503:\n return \"service_unavailable\";\n default:\n return \"internal_error\";\n }\n }\n\n private normalizeError(err: unknown): Error {\n if (err instanceof OrakleError) {\n return err;\n }\n\n if (err instanceof SSEHttpError) {\n let body: ApiErrorBody | undefined;\n try {\n body = JSON.parse(err.body) as ApiErrorBody;\n } catch {\n // Not JSON\n }\n const code = body?.code ?? this.httpStatusToErrorCode(err.status);\n const message =\n body?.message ?? `API request failed with status ${err.status}`;\n return new OrakleApiError(message, err.status, code);\n }\n\n // AbortError from timeout\n if (err instanceof DOMException && err.name === \"AbortError\") {\n // Check if our timeout caused the abort\n return new OrakleTimeoutError(this.timeout);\n }\n\n if (err instanceof Error) {\n return new OrakleConnectionError(err.message, err);\n }\n\n return new OrakleConnectionError(String(err));\n }\n}\n"],"mappings":"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","names":["body"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orakle",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "description": "Client SDK for orakle.xyz",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@biomejs/biome": "^1.9.4",
31
+ "@biomejs/biome": "2.4.12",
32
32
  "@types/node": "^22.15.3",
33
33
  "tsup": "^8.4.0",
34
34
  "typescript": "^5.8.3",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",
39
- "typecheck": "tsc --noEmit",
39
+ "check": "tsc --noEmit && biome check",
40
40
  "test": "vitest run",
41
41
  "test:watch": "vitest"
42
42
  }