@spreadspace/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/transport.ts","../src/errors.ts","../src/money.ts","../src/version.ts","../src/helpers/pagination.ts","../src/helpers/operations.ts","../src/helpers/upload.ts","../src/webhooks.ts","../src/resources/webhooks.ts","../src/resources/embed.ts","../src/client.ts"],"sourcesContent":["/**\n * @spreadspace/sdk — public entry point.\n *\n * import { SpreadSpace } from '@spreadspace/sdk';\n * const client = new SpreadSpace({ apiKey: 'ss_test_...' });\n * for await (const borrower of client.borrowers.list()) {\n * // ...\n * }\n *\n * Re-exports the client facade, the transport, the error hierarchy, the helper\n * types/classes (pagination, async operations, upload), the exact-money type,\n * and version constants.\n *\n * Money note: amounts decode to an exact `Decimal` (re-exported from\n * `decimal.js`), not a JS `number`. Do exact math with `.plus()`, `.toFixed(2)`,\n * etc.; send a value back to the API with `.toNumber()` (the wire stays a JSON\n * number). See the README \"Money\" section for the (payload-only) coverage edge.\n */\n\n// -- client facade ----------------------------------------------------------\nexport {\n SpreadSpace,\n type SpreadSpaceOptions,\n type ListBorrowersParams,\n type ListLoansParams,\n type ListJobsParams,\n type ListAsyncOperationsParams,\n} from './client.js';\n\n// -- transport --------------------------------------------------------------\nexport {\n Transport,\n DEFAULT_BASE_URL,\n type TransportOptions,\n type HttpMethod,\n} from './transport.js';\n\n// -- errors -----------------------------------------------------------------\nexport {\n SpreadSpaceError,\n InvalidRequestError,\n AuthenticationError,\n PermissionError,\n NotFoundError,\n ConflictError,\n RateLimitError,\n ServerError,\n NetworkError,\n classifyError,\n type SpreadSpaceErrorParams,\n} from './errors.js';\n\n// -- shared wire types ------------------------------------------------------\nexport type {\n CursorPaginated,\n QueryParams,\n RequestOptions,\n InternalRequestOptions,\n} from './types.js';\n\n// -- helpers: pagination ----------------------------------------------------\nexport {\n paginate,\n type AsyncPager,\n type PaginateOptions,\n} from './helpers/pagination.js';\n\n// -- helpers: async operations ----------------------------------------------\nexport {\n AsyncOperationHandle,\n AsyncOperationError,\n AsyncOperationTimeout,\n createExtractionExport,\n getOperation,\n cancelOperation,\n isTerminal,\n TERMINAL_STATUSES,\n type AsyncOperation,\n type ExtractionExportParams,\n type WaitOptions as OperationWaitOptions,\n type OperationsTransport,\n} from './helpers/operations.js';\n\n// -- helpers: upload --------------------------------------------------------\nexport {\n JobHandle,\n UploadError,\n UploadTimeout,\n uploadDocument,\n parsePresignedUrl,\n TERMINAL_JOB_STATUSES,\n type PresignedUrl,\n type UploadOptions,\n type UploadSource,\n type WaitOptions as UploadWaitOptions,\n} from './helpers/upload.js';\n\n// -- webhooks: signature verifier + typed events ----------------------------\n// Also importable tree-shakeably from '@spreadspace/sdk/webhooks'.\nexport {\n verifyWebhook,\n verifyAndParseWebhook,\n WebhookSignatureError,\n type VerifyWebhookOptions,\n type WebhookEvent,\n type WebhookEventType,\n type VerifyAndParseResult,\n type DocumentProcessedPayload,\n type DocumentFailedPayload,\n type ExtractionReadyPayload,\n type JobCompletedPayload,\n type LoanClassifiedPayload,\n} from './webhooks.js';\n\n// -- resources: webhooks management -----------------------------------------\nexport { WebhooksResource, type WebhookListParams } from './resources/webhooks.js';\n\n// -- resources: embed -------------------------------------------------------\nexport {\n EmbedResource,\n EmbedSessionsResource,\n EmbedIframeUrlsResource,\n type CreateEmbedSessionParams,\n type EmbedSessionResult,\n type CreateEmbedIframeUrlParams,\n type EmbedIframeUrlResult,\n} from './resources/embed.js';\n\n// -- money: exact-decimal type + allowlist ----------------------------------\n// Money fields decode to `Decimal` (exact), not `number`. `Money` is the public\n// alias; the key sets document which fields are converted (see money.ts).\nexport {\n Decimal,\n parseBodyWithExactMoney,\n SCALAR_MONEY_KEYS,\n PAYLOAD_MONEY_KEYS,\n type Money,\n} from './money.js';\n\n// -- version ----------------------------------------------------------------\nexport { SDK_VERSION, DEFAULT_API_VERSION } from './version.js';\n","/**\n * HTTP transport: auth, version header, idempotency, retries, error mapping.\n *\n * The configured client every helper sits on top of. Hand-written, not\n * generated. Responsibilities:\n *\n * - `Authorization: Bearer <key>` + `SpreadSpace-Version` on every API request.\n * - Auto `Idempotency-Key` (uuid) on non-GET methods, suppressible per call.\n * - Retry 429 + 5xx + transport errors with exponential backoff + full jitter,\n * honoring `Retry-After`.\n * - Map the canonical error envelope to typed errors carrying `requestId`,\n * preferring the `X-Request-ID` response header.\n *\n * `fetch` and `sleep` are injectable so unit tests stub the network and never\n * actually delay. Success bodies decode via `parseBodyWithExactMoney` so money\n * fields come back as exact `Decimal`s (lossless-json + decimal.js) — the wire\n * is unchanged (money stays a JSON number); only the decoded JS type differs.\n * The error envelope still uses plain `JSON.parse` (no money there).\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport {\n classifyError,\n NetworkError,\n SpreadSpaceError,\n} from './errors.js';\nimport { parseBodyWithExactMoney } from './money.js';\nimport type { InternalRequestOptions, QueryParams } from './types.js';\nimport { DEFAULT_API_VERSION, SDK_VERSION } from './version.js';\n\nexport const DEFAULT_BASE_URL = 'https://api.spreadspace.ai';\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\nconst DEFAULT_MAX_RETRIES = 2;\n\n// Backoff tuning, mirrored across the SDKs (base 500ms, cap 30s, full jitter).\nconst RETRY_BASE_DELAY_MS = 500;\nconst RETRY_MAX_DELAY_MS = 30_000;\n\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n\nexport interface TransportOptions {\n /** API key. `ss_live_` -> live tenant, `ss_test_` -> sandbox tenant. */\n apiKey: string;\n\n /** Base URL. Defaults to `https://api.spreadspace.ai` (trailing slash trimmed). */\n baseUrl?: string;\n\n /** Override the SDK-pinned `SpreadSpace-Version` header. */\n apiVersion?: string;\n\n /** Per-attempt request timeout in ms. Default 60000. Retries get a fresh window. */\n timeout?: number;\n\n /**\n * Max retry attempts after the initial request. Default 2. Retries on 429,\n * 5xx, and transport failures; other 4xx never retry.\n */\n maxRetries?: number;\n\n /** `fetch` implementation. Defaults to `globalThis.fetch`. Inject a mock in tests. */\n fetch?: typeof fetch;\n\n /** Sleep used between retries. Injectable so tests don't actually wait. */\n sleep?: (ms: number) => Promise<void>;\n\n /** Idempotency-key generator. Defaults to `crypto.randomUUID()`. */\n idempotencyKeyGenerator?: () => string;\n}\n\n/** Configured HTTP client wrapping `fetch` with SpreadSpace conventions. */\nexport class Transport {\n /** Resolved base URL (no trailing slash). */\n public readonly baseUrl: string;\n\n /** Resolved API version (the `SpreadSpace-Version` header value). */\n public readonly apiVersion: string;\n\n /** Default retry ceiling. Public so helpers can construct URLs / pagers off it. */\n public readonly maxRetries: number;\n\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly sleepImpl: (ms: number) => Promise<void>;\n private readonly idempotencyKeyGenerator: () => string;\n private readonly userAgent: string;\n\n constructor(options: TransportOptions) {\n if (!options || typeof options.apiKey !== 'string' || options.apiKey.length === 0) {\n throw new TypeError(\n 'Transport requires an apiKey. Issue one from the dashboard at /settings/api-keys.',\n );\n }\n\n this.apiKey = options.apiKey;\n let baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n while (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);\n this.baseUrl = baseUrl;\n this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;\n this.timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.sleepImpl = options.sleep ?? defaultSleep;\n this.idempotencyKeyGenerator = options.idempotencyKeyGenerator ?? randomUUID;\n this.userAgent = `spreadspace-sdk/${SDK_VERSION} node/${nodeVersion()}`;\n\n if (typeof this.fetchImpl !== 'function') {\n throw new TypeError(\n 'Transport requires a fetch implementation. Node 18+ provides one globally; pass a polyfill via options.fetch otherwise.',\n );\n }\n }\n\n /**\n * Issue an API request and return the decoded JSON body (or `undefined` for\n * an empty / 204 response). Integrators can call this directly to hit\n * endpoints not yet surfaced via a typed resource.\n */\n public async request<T>(\n method: HttpMethod,\n path: string,\n options: InternalRequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.query);\n const headers = this.buildHeaders(method, options);\n const body = options.body !== undefined ? JSON.stringify(options.body) : undefined;\n\n const response = await this.send(url, { method, headers, body }, {\n maxRetries: options.maxRetries ?? this.maxRetries,\n signal: options.signal,\n describe: `${method} ${url}`,\n });\n\n if (response.ok) {\n return await this.parseSuccessBody<T>(response);\n }\n const requestId = response.headers.get('X-Request-ID') ?? undefined;\n throw await this.buildErrorFromResponse(response, requestId);\n }\n\n /**\n * Single retry path shared by `request()` and `putPresigned()` (mirrors the\n * Python `_send`). Issues `fetch` with a per-attempt timeout, retrying 429 +\n * 5xx + transport failures on the full-jitter backoff curve, honoring\n * `Retry-After`. Returns the final `Response` (success OR a non-retryable /\n * retries-exhausted error status — callers decide what to do with it); throws\n * `NetworkError` only when transport failures exhaust retries.\n */\n private async send(\n url: string,\n init: { method: string; headers: HeadersInit; body?: BodyInit | null },\n opts: { maxRetries: number; signal?: AbortSignal; describe: string },\n ): Promise<Response> {\n const { maxRetries, signal: callerSignal, describe } = opts;\n let attempt = 0;\n let lastError: unknown;\n\n while (attempt <= maxRetries) {\n // Per-attempt timeout, composed with any caller-provided signal so\n // abort-from-outside still works.\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => controller.abort(), this.timeoutMs);\n const signal = callerSignal\n ? composeAbortSignals(controller.signal, callerSignal)\n : controller.signal;\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, { ...init, signal });\n } catch (err) {\n clearTimeout(timeoutHandle);\n lastError = err;\n // Transport failures retry on the same curve as 5xx — DNS hiccups, TLS\n // handshake failures, intermittent resets are what retries cover.\n if (attempt < maxRetries) {\n await this.sleepImpl(this.computeRetryDelay(attempt, undefined));\n attempt += 1;\n continue;\n }\n throw new NetworkError(`SpreadSpace request to ${describe} failed: ${describeError(err)}`, err);\n }\n\n clearTimeout(timeoutHandle);\n\n const status = response.status;\n const retryable = status === 429 || (status >= 500 && status <= 599);\n if (retryable && attempt < maxRetries) {\n const retryAfterSec = parseRetryAfter(response.headers.get('Retry-After'));\n await this.sleepImpl(this.computeRetryDelay(attempt, retryAfterSec));\n attempt += 1;\n // Drain the body so the connection can be reused.\n try {\n await response.body?.cancel();\n } catch {\n /* ignore */\n }\n continue;\n }\n\n return response;\n }\n\n // Unreachable — the loop returns, throws, or continues.\n throw new NetworkError(\n `SpreadSpace request to ${describe} exhausted retries (last error: ${describeError(lastError)}).`,\n lastError,\n );\n }\n\n /**\n * PUT raw bytes straight to a presigned S3 URL (out-of-band, no auth).\n *\n * `contentType` MUST equal the value sent when minting the URL — it is part\n * of the V4 signature. No SSE headers (bucket-default KMS applies). No\n * Authorization / version headers; this does not hit a SpreadSpace endpoint.\n * Retries on transport/5xx like `request()`; throws `SpreadSpaceError` on a\n * non-2xx S3 status.\n */\n public async putPresigned(\n url: string,\n data: BodyInit,\n contentType: string,\n options: { maxRetries?: number; signal?: AbortSignal } = {},\n ): Promise<Response> {\n // Same retry path as request(): retries 429 + 5xx + transport on the\n // full-jitter curve, honoring Retry-After. Mirrors Python's put_presigned.\n const response = await this.send(\n url,\n { method: 'PUT', headers: { 'Content-Type': contentType }, body: data },\n {\n maxRetries: options.maxRetries ?? this.maxRetries,\n signal: options.signal,\n describe: `PUT ${url}`,\n },\n );\n\n if (response.ok) {\n return response;\n }\n\n const text = await safeReadText(response);\n throw new SpreadSpaceError({\n type: 'presigned_upload_failed',\n message: `Presigned upload failed: HTTP ${response.status}`,\n statusCode: response.status,\n rawBody: text,\n });\n }\n\n // -- internals ----------------------------------------------------------\n\n private buildUrl(path: string, query?: QueryParams): string {\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n const url = new URL(this.baseUrl + normalizedPath);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n }\n\n private buildHeaders(method: HttpMethod, options: InternalRequestOptions): Headers {\n const headers = new Headers();\n headers.set('Authorization', `Bearer ${this.apiKey}`);\n headers.set('SpreadSpace-Version', options.apiVersion ?? this.apiVersion);\n headers.set('Accept', 'application/json');\n headers.set('User-Agent', this.userAgent);\n\n if (options.body !== undefined) {\n headers.set('Content-Type', 'application/json; charset=utf-8');\n }\n\n // Idempotency-Key on every non-safe method unless explicitly suppressed via\n // `idempotencyKey: null` or already provided.\n if (!SAFE_METHODS.has(method)) {\n const explicit = options.idempotencyKey;\n if (explicit === null) {\n // Caller opted out — no header.\n } else if (typeof explicit === 'string' && explicit.length > 0) {\n headers.set('Idempotency-Key', explicit);\n } else {\n headers.set('Idempotency-Key', this.idempotencyKeyGenerator());\n }\n }\n\n return headers;\n }\n\n /**\n * Next retry delay. Exponential backoff with full jitter, floored by\n * `Retry-After` when the server provided one (never retry faster than asked).\n *\n * base = min(maxDelay, baseDelay * 2^attempt)\n * delay = random(0, base)\n *\n * Full jitter (AWS-recommended) minimizes thundering-herd across clients.\n */\n private computeRetryDelay(attempt: number, retryAfterSec: number | undefined): number {\n const expBackoff = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);\n const jittered = Math.floor(Math.random() * expBackoff);\n\n if (retryAfterSec !== undefined && retryAfterSec > 0) {\n return Math.max(jittered, retryAfterSec * 1000);\n }\n return jittered;\n }\n\n private async parseSuccessBody<T>(response: Response): Promise<T> {\n if (response.status === 204) {\n return undefined as T;\n }\n const text = await response.text();\n if (text.length === 0) {\n return undefined as T;\n }\n try {\n // Lossless decode: money fields come back as exact `Decimal`s, every\n // other numeric stays a plain `number`. Wire is unchanged. See money.ts.\n return parseBodyWithExactMoney(text) as T;\n } catch (err) {\n throw new NetworkError(\n `Failed to parse SpreadSpace response as JSON (status=${response.status}): ${describeError(err)}`,\n err,\n );\n }\n }\n\n private async buildErrorFromResponse(\n response: Response,\n requestId: string | undefined,\n ): Promise<SpreadSpaceError> {\n const status = response.status;\n let rawBody: unknown;\n let type = 'unknown';\n let message = `SpreadSpace API request failed with status ${status}.`;\n let details: Record<string, string> | undefined;\n let resolvedRequestId = requestId;\n\n try {\n const text = await response.text();\n if (text.length > 0) {\n try {\n const parsed = JSON.parse(text) as unknown;\n rawBody = parsed;\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as { error: unknown }).error === 'object' &&\n (parsed as { error: object }).error !== null\n ) {\n const errBody = (parsed as { error: Record<string, unknown> }).error;\n if (typeof errBody.type === 'string') type = errBody.type;\n if (typeof errBody.message === 'string') message = errBody.message;\n if (\n errBody.details &&\n typeof errBody.details === 'object' &&\n !Array.isArray(errBody.details)\n ) {\n details = errBody.details as Record<string, string>;\n }\n // Header `X-Request-ID` is authoritative; fall through to the body's\n // `request_id` only when the header is absent (mocks, proxies).\n if (resolvedRequestId === undefined && typeof errBody.request_id === 'string') {\n resolvedRequestId = errBody.request_id;\n }\n }\n } catch {\n rawBody = text;\n }\n }\n } catch {\n // Body read failure — keep the generic message.\n }\n\n const retryAfter = parseRetryAfter(response.headers.get('Retry-After'));\n const ErrorClass = classifyError(status, type);\n return new ErrorClass({\n type,\n message,\n statusCode: status,\n requestId: resolvedRequestId,\n rawBody,\n details,\n retryAfter,\n });\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Helpers — module-private; not exported.\n// ──────────────────────────────────────────────────────────────────────────\n\nfunction defaultSleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction nodeVersion(): string {\n return typeof process !== 'undefined' && process.version ? process.version : 'unknown';\n}\n\n/**\n * Parse `Retry-After`. Spec allows delta-seconds or HTTP-date; we accept both.\n * Returns `undefined` if missing or unparseable.\n */\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (value === null || value.length === 0) return undefined;\n const asInt = Number(value);\n if (Number.isFinite(asInt) && Number.isInteger(asInt) && asInt >= 0) {\n return asInt;\n }\n const asDate = Date.parse(value);\n if (!Number.isNaN(asDate)) {\n const deltaMs = asDate - Date.now();\n if (deltaMs > 0) return Math.ceil(deltaMs / 1000);\n }\n return undefined;\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return String(err);\n}\n\nasync function safeReadText(response: Response): Promise<string | undefined> {\n try {\n return await response.text();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Compose two `AbortSignal`s — abort either and the result fires. Prefer the\n * standard `AbortSignal.any` (Node 20+); polyfill for Node 18.\n */\nfunction composeAbortSignals(a: AbortSignal, b: AbortSignal): AbortSignal {\n type AbortSignalAny = (signals: AbortSignal[]) => AbortSignal;\n const maybeAny = (AbortSignal as unknown as { any?: AbortSignalAny }).any;\n if (typeof maybeAny === 'function') {\n return maybeAny([a, b]);\n }\n\n const controller = new AbortController();\n const onAbort = (source: AbortSignal): void => {\n if (controller.signal.aborted) return;\n const reason = (source as { reason?: unknown }).reason;\n controller.abort(reason);\n };\n\n if (a.aborted) onAbort(a);\n else a.addEventListener('abort', () => onAbort(a), { once: true });\n if (b.aborted) onAbort(b);\n else b.addEventListener('abort', () => onAbort(b), { once: true });\n return controller.signal;\n}\n","/**\n * Public error hierarchy for the SpreadSpace SDK.\n *\n * Any non-2xx API response is thrown as one of the subclasses below so\n * integrators pattern-match on the type without parsing bodies. Shape mirrors\n * the canonical envelope:\n *\n * { error: { type, message, request_id, details? } }\n *\n * `requestId` is preserved on every thrown error (prefer the `X-Request-ID`\n * response header over the body's `request_id`). Transport-level failures\n * surface as `NetworkError`.\n */\n\nexport interface SpreadSpaceErrorParams {\n type: string;\n message: string;\n statusCode: number;\n requestId?: string;\n rawBody?: unknown;\n details?: Record<string, string>;\n /** `Retry-After` seconds echoed from a 429/503, when present. */\n retryAfter?: number;\n}\n\n/** Base class for every API-shaped error. */\nexport class SpreadSpaceError extends Error {\n /** Stable machine code from `error.type` (match on this, never `message`). */\n public readonly type: string;\n\n /** HTTP status code. */\n public readonly statusCode: number;\n\n /** `X-Request-ID` echoed from the server. Quote in support tickets. */\n public readonly requestId?: string;\n\n /** Raw response body for debugging — parsed JSON or a string. */\n public readonly rawBody?: unknown;\n\n /** Optional structured details (e.g. a field name on a validation error). */\n public readonly details?: Record<string, string>;\n\n /** `Retry-After` seconds, when the server provided one. */\n public readonly retryAfter?: number;\n\n constructor(params: SpreadSpaceErrorParams) {\n super(params.message);\n this.name = new.target.name;\n this.type = params.type;\n this.statusCode = params.statusCode;\n this.requestId = params.requestId;\n this.rawBody = params.rawBody;\n this.details = params.details;\n this.retryAfter = params.retryAfter;\n\n // Maintain the prototype chain so `instanceof` survives transpilation to\n // ES5 (some integrator builds still target older runtimes).\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 400 Bad Request — typically schema validation or an invalid cursor/version. */\nexport class InvalidRequestError extends SpreadSpaceError {}\n\n/** 401 Unauthorized — missing, expired, or invalid API key. */\nexport class AuthenticationError extends SpreadSpaceError {}\n\n/** 403 Forbidden — authenticated but not allowed (scope, plan, mode, PII claim). */\nexport class PermissionError extends SpreadSpaceError {}\n\n/** 404 Not Found. */\nexport class NotFoundError extends SpreadSpaceError {}\n\n/** 409 Conflict — idempotency-key mismatch or cancel-on-terminal-operation. */\nexport class ConflictError extends SpreadSpaceError {}\n\n/**\n * 429 Too Many Requests. The SDK retries automatically up to `maxRetries`,\n * honoring `Retry-After`; this surfaces only when retries are exhausted.\n */\nexport class RateLimitError extends SpreadSpaceError {}\n\n/** 5xx Server Error. Retries are exhausted by the time this surfaces. */\nexport class ServerError extends SpreadSpaceError {}\n\n/**\n * Transport-level failure — DNS, TCP reset, fetch abort, or a JSON parse\n * failure on a 2xx response. Distinct from API-shaped errors.\n */\nexport class NetworkError extends Error {\n public readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = 'NetworkError';\n this.cause = cause;\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\n/**\n * Map an HTTP status + canonical `error.type` to an error class. Status is the\n * primary signal (a bodyless 500 still becomes `ServerError`); `type` is\n * accepted for future sharpening at the same status. Mapping:\n * 400->InvalidRequest, 401->Authentication, 403->Permission, 404->NotFound,\n * 409->Conflict, 429->RateLimit, >=500->Server. Unlisted 4xx (e.g. the 402\n * billing wall) -> the generic `SpreadSpaceError` base — NOT InvalidRequest,\n * which would misrepresent them as 400-style validation errors. Mirrors the\n * Python `classify` fallthrough (unlisted 4xx -> APIStatusError base).\n */\nexport function classifyError(\n statusCode: number,\n _type: string | undefined,\n): typeof SpreadSpaceError {\n if (statusCode === 400) return InvalidRequestError;\n if (statusCode === 401) return AuthenticationError;\n if (statusCode === 403) return PermissionError;\n if (statusCode === 404) return NotFoundError;\n if (statusCode === 409) return ConflictError;\n if (statusCode === 429) return RateLimitError;\n if (statusCode >= 500) return ServerError;\n return SpreadSpaceError;\n}\n","/**\n * Exact-decimal money decoding.\n *\n * The SpreadSpace wire encodes money as a JSON `number`. Routed through the\n * stock `JSON.parse`, every amount is forced through IEEE-754 float64 the\n * instant it is read — so `0.1 + 0.2`-class drift and >2^53 truncation are\n * baked in before any caller touches the value. Python (`parse_float=Decimal`)\n * and C# (`System.Decimal`) already read money exactly; only TS was lossy.\n *\n * This module closes that gap WITHOUT touching the wire (money stays a JSON\n * number outbound and inbound — non-breaking). It parses the body with\n * `lossless-json`, which keeps every numeric literal as a string-backed\n * {@link LosslessNumber} (no float64 round-trip), then deep-walks the decoded\n * tree converting money fields to a `decimal.js` {@link Decimal} and every other\n * numeric back to a plain `number`. The net effect: money is exact, everything\n * else keeps its ordinary `number` type.\n *\n * ## Matching strategy (two tiers, one walk)\n *\n * - **Tier 1 — {@link SCALAR_MONEY_KEYS} (global).** The named scalar money\n * fields on the typed public surface (`net_amount`, `requested_amount`, …).\n * All are `decimal`/`decimal?` server-side and none collide with a non-money\n * typed field, so they are converted wherever they appear. Complete + exact.\n *\n * - **Tier 2 — {@link PAYLOAD_MONEY_KEYS} (scoped).** The money key names that\n * live ONLY inside the opaque `payload` blob (the spread / P&L / cash-flow /\n * aging / bank-analysis report tree — `values`, `vals`, `total`, `net`, …).\n * Several are generic words (`total`, `net`, `value`) that DO collide with\n * non-money numbers elsewhere — e.g. `progress.total` is an int counter — so\n * they are converted ONLY once the walk has descended through a `payload`\n * key. Scoping is what makes including those generic names safe.\n *\n * `span` (the `BankCounterpartyTransactionDto.span` bounding box) is deliberately\n * NOT a money scope: its numeric keys (`x0`, `top`, `x1`, `bottom`,\n * `page_width`, `page_height`) are PDF geometry, never dollars. It carries no\n * Tier-2 key names, so the default (non-payload) handling leaves it as plain\n * numbers — exactly right.\n *\n * ## Honest coverage\n *\n * Tier 1 is complete and exact. Tier 2 is a heuristic keyed on field names\n * inside a fully-opaque `JsonElement`: it covers the entire *current* payload\n * money vocabulary, but if the server later emits a NEW money key inside the\n * payload under a name not in {@link PAYLOAD_MONEY_KEYS}, that one field is\n * handed back as a plain `number` (lossy) until the set is extended. The failure\n * mode is \"a future field stays float64\", never \"a non-money field becomes a\n * Decimal\" — non-payload numbers outside Tier 1 are never touched.\n */\n\nimport { Decimal } from 'decimal.js';\nimport { isLosslessNumber, parse, type LosslessNumber } from 'lossless-json';\n\n/** Re-exported so consumers can do exact arithmetic on money fields. */\nexport { Decimal };\n\n/**\n * The decoded runtime type of a money field: an arbitrary-precision decimal.\n * Matches the Python / C# semantics (`Decimal` / `System.Decimal`). Callers do\n * `amount.toFixed(2)`, `amount.plus(other)`, `amount.toString()`; to send a\n * value back to the API (wire stays a JSON number) use `amount.toNumber()`.\n */\nexport type Money = Decimal;\n\n/**\n * Tier 1 — scalar money field names, converted wherever they appear in a\n * response. Every one is `decimal`/`decimal?` server-side and unambiguous\n * across the public surface (none names a non-money typed field). Sourced from\n * `api/src/Models/*.cs` (the C# declared type is ground truth — `System.Decimal`\n * serializes as a JSON number, so `format: double` in OpenAPI cannot tell money\n * from rate on its own).\n */\nexport const SCALAR_MONEY_KEYS: ReadonlySet<string> = new Set([\n // BankCounterparty* (direction splits, rollups, relationship groups, review\n // items, merge/split responses).\n 'net_amount',\n 'inflow_amount',\n 'outflow_amount',\n 'amount',\n 'advance_amount',\n 'remittance_amount',\n // BankCounterpartyRelationshipGroup.cost_of_capital — a dollar figure\n // (`net < 0 ? -net : 0`), NOT a percentage. Money.\n 'cost_of_capital',\n // Loan create/update/response.\n 'requested_amount',\n // BorrowerResponse facility-size aggregates.\n 'mean_facility_size',\n 'median_facility_size',\n // DocumentLogEntry / usage / job-status billing figures (USD).\n 'billed_amount_usd',\n 'estimated_cost_usd',\n]);\n\n/**\n * Tier 2 — money key names that exist ONLY inside the opaque `payload` blob\n * (`LoanSpreadEnvelope.payload` — the spread / tax / P&L / cash-flow / aging /\n * bank-analysis report tree). Each is `decimal` or `List<decimal>` server-side\n * (`ExtractionDtos.cs`, `Spreading/PayloadBank.cs`). Converted ONLY within a\n * `payload` subtree — several names (`total`, `net`, `value`) collide with\n * non-money numbers at the top level, so the scope guard is load-bearing.\n *\n * Array elements count: a `List<decimal>` (e.g. `values`, `vals`, `total_vals`,\n * a `total` row) decodes to an array whose every numeric element is money.\n */\nexport const PAYLOAD_MONEY_KEYS: ReadonlySet<string> = new Set([\n // P&L / cash-flow / balance-sheet rows (List<decimal>).\n 'values',\n 'total',\n // AR/AP aging rows (List<decimal>).\n 'vals',\n 'total_vals',\n // Bank-statement period summary (scalar decimal each).\n 'deposits',\n 'withdrawals',\n 'fees',\n 'interest',\n 'net',\n // Generic leaf amount inside the payload tree.\n 'amount',\n // Bank TTM combined month / summary tiles (Spreading/PayloadBank.cs).\n 'total_deposits',\n 'total_withdrawals',\n 'net_change',\n 'total_deposits_ex_internal_transfers',\n 'total_withdrawals_ex_internal_transfers',\n 'net_change_ex_internal_transfers',\n 'ending_balance',\n 'value',\n]);\n\n/**\n * The keys whose subtree switches the walk into \"payload mode\" (Tier 2 active).\n * `LoanSpreadEnvelope.payload` is the opaque spread/report blob. Once inside, it\n * stays inside for all descendants — nested report sections, rows, and their\n * `List<decimal>` arrays are all money-bearing under {@link PAYLOAD_MONEY_KEYS}.\n */\nconst PAYLOAD_SCOPE_KEYS: ReadonlySet<string> = new Set(['payload']);\n\n/**\n * Parse a SpreadSpace JSON response body with exact-decimal money.\n *\n * Drop-in for `JSON.parse(text)` on the success path: same return shape, except\n * money fields are {@link Decimal} instead of `number`. Throws the same\n * `SyntaxError`-family error as `JSON.parse` on malformed JSON (callers map it\n * to a `NetworkError`), so error handling upstream is unchanged.\n */\nexport function parseBodyWithExactMoney(text: string): unknown {\n // Parse with the default number parser so EVERY numeric literal lands as a\n // string-backed LosslessNumber (no float64 round-trip yet); the walk then\n // decides Decimal-vs-number per key. We don't use the `reviver` seam because\n // it only sees `(key, value)` — no path — and Tier 2 needs to know whether it\n // is inside a `payload` subtree.\n const tree = parse(text);\n return convertDeep(tree, false);\n}\n\n/**\n * Recursively convert a decoded lossless tree into the public shape.\n *\n * `inPayload` is `true` once the walk has descended through a {@link\n * PAYLOAD_SCOPE_KEYS} key; it stays `true` for every descendant. A numeric leaf\n * becomes a {@link Decimal} when its key is money (Tier 1 always, Tier 2 only\n * when `inPayload`), otherwise a plain `number` via `.valueOf()`.\n *\n * @param value the node to convert (object, array, LosslessNumber, or scalar)\n * @param inPayload whether we are inside a payload subtree (Tier 2 active)\n * @param key the property name this node was reached under (drives money match)\n * @param inMoneyArray whether the parent is a money-keyed array, so every\n * element is money (a `List<decimal>` row)\n */\nfunction convertDeep(\n value: unknown,\n inPayload: boolean,\n key?: string,\n inMoneyArray = false,\n): unknown {\n // Numeric leaf — the only place a number→Decimal decision is made.\n if (isLosslessNumber(value)) {\n if (inMoneyArray || isMoneyKey(key, inPayload)) {\n return losslessToDecimal(value);\n }\n return losslessToNumber(value);\n }\n\n if (Array.isArray(value)) {\n // A money-keyed array (e.g. `values`/`vals`/`total`) is a List<decimal>:\n // every element is money. Otherwise descend element-wise, carrying the\n // array's own key so a non-money array of objects keeps its key context\n // off the elements (elements are objects/scalars, not keyed money leaves).\n const elementIsMoney = isMoneyKey(key, inPayload);\n return value.map((item) => convertDeep(item, inPayload, undefined, elementIsMoney));\n }\n\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const [childKey, childValue] of Object.entries(value)) {\n // Entering a payload subtree latches Tier 2 on for all descendants.\n const childInPayload = inPayload || PAYLOAD_SCOPE_KEYS.has(childKey);\n out[childKey] = convertDeep(childValue, childInPayload, childKey, false);\n }\n return out;\n }\n\n // Strings, booleans, null, undefined — pass through untouched.\n return value;\n}\n\n/** Whether a field name denotes money in the current scope. */\nfunction isMoneyKey(key: string | undefined, inPayload: boolean): boolean {\n if (key === undefined) return false;\n if (SCALAR_MONEY_KEYS.has(key)) return true;\n return inPayload && PAYLOAD_MONEY_KEYS.has(key);\n}\n\n/**\n * Convert a LosslessNumber to an exact {@link Decimal} from its source string —\n * the float64 round-trip is never taken, so `0.1`, `9007199254740993`, and long\n * cent sums are preserved exactly.\n */\nfunction losslessToDecimal(value: LosslessNumber): Decimal {\n return new Decimal(value.toString());\n}\n\n/**\n * Convert a LosslessNumber back to a plain `number` (the ordinary type for every\n * non-money numeric). Uses `.valueOf()`, which returns a `bigint` for integers\n * too large for `number`; those are coerced to `number` to preserve the legacy\n * `JSON.parse` shape (a bare `number`) for non-money fields — callers that need\n * exactness there are out of scope (only money is promised exact).\n */\nfunction losslessToNumber(value: LosslessNumber): number {\n const v = value.valueOf();\n return typeof v === 'bigint' ? Number(v) : v;\n}\n\n/** Plain-object guard (excludes arrays, null, and class instances). */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","// Version constants. SDK_VERSION is the published package version; the dated\n// DEFAULT_API_VERSION is the SpreadSpace-Version this release pins by default\n// (overridable per request). Bump SDK_VERSION in lockstep with package.json.\n\nexport const SDK_VERSION = '0.1.0';\n\n// Latest supported API version as of this SDK release. Mirrors\n// ApiVersionRegistry.Latest on the server.\nexport const DEFAULT_API_VERSION = '2026-05-03';\n","/**\n * Cursor pagination over `CursorPaginated<T>` list envelopes.\n *\n * The API pages with `{ data: T[], limit: number, next_cursor: string | null }`;\n * the ONLY end-of-stream signal is `next_cursor === null` (no `has_more`, no\n * `total`). One route diverges (`GET /api/async-operations`: items under\n * `operations`), so `itemsKey` is overridable. The returned pager is an\n * `AsyncIterable<T>` that yields items lazily, auto-fetching pages until the\n * cursor is exhausted, plus `.pages()` (envelope-at-a-time) and `.toArray()`.\n */\n\nimport type { Transport } from '../transport.js';\nimport type { CursorPaginated, QueryParams } from '../types.js';\n\n/**\n * Lazy pager over a cursor-paginated list endpoint. Three ways to consume:\n *\n * 1. **`for await`** — every item across all pages (the common case).\n * 2. **`.pages()`** — one `CursorPaginated<T>` envelope per HTTP round-trip.\n * 3. **`.toArray()`** — eagerly drain into a single array (bounded queries only).\n */\nexport interface AsyncPager<T> extends AsyncIterable<T> {\n /** Page-by-page async iterator; each value is a full envelope. */\n pages(): AsyncIterableIterator<CursorPaginated<T>>;\n\n /** Eagerly drain every page into one array. Throws if any request fails. */\n toArray(): Promise<T[]>;\n}\n\n/** Options for {@link paginate}. */\nexport interface PaginateOptions {\n /** Base query params, merged into every page request. Never mutated. */\n params?: QueryParams;\n\n /** Response key holding the item array. `\"data\"`, or `\"operations\"` for async-ops. */\n itemsKey?: string;\n\n /** Query param carrying the opaque next-page token. Default `\"cursor\"`. */\n cursorParam?: string;\n\n /** Override the `SpreadSpace-Version` header for every page request. */\n apiVersion?: string;\n}\n\n/** Build a pager that walks a cursor-paginated list endpoint. */\nexport function paginate<T>(\n transport: Transport,\n path: string,\n options: PaginateOptions = {},\n): AsyncPager<T> {\n const itemsKey = options.itemsKey ?? 'data';\n const cursorParam = options.cursorParam ?? 'cursor';\n const apiVersion = options.apiVersion;\n // Copy so we never mutate the caller's params when overlaying the cursor.\n const baseParams: QueryParams = { ...(options.params ?? {}) };\n\n // Per-page async iterator — one HTTP call per yield, until the cursor exhausts.\n async function* pageIterator(): AsyncGenerator<CursorPaginated<T>, void, undefined> {\n let cursor: string | undefined;\n while (true) {\n const query: QueryParams = { ...baseParams };\n if (cursor !== undefined) query[cursorParam] = cursor;\n\n const page = await transport.request<CursorPaginated<T>>('GET', path, {\n query,\n apiVersion,\n });\n if (page === null || typeof page !== 'object') return;\n yield page;\n\n const next = page.next_cursor ?? null;\n if (!next) return;\n // Defensive: a page echoing its own cursor would loop forever.\n if (next === cursor) return;\n cursor = next;\n }\n }\n\n // Per-item async iterator — drains pages internally, yields each item.\n async function* itemIterator(): AsyncGenerator<T, void, undefined> {\n for await (const page of pageIterator()) {\n const items = (page as unknown as Record<string, unknown>)[itemsKey] as T[] | undefined;\n if (!items) continue;\n yield* items;\n }\n }\n\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return itemIterator();\n },\n pages(): AsyncIterableIterator<CursorPaginated<T>> {\n return pageIterator();\n },\n async toArray(): Promise<T[]> {\n const out: T[] = [];\n for await (const item of itemIterator()) out.push(item);\n return out;\n },\n };\n}\n","/**\n * Async operations: extraction-export create + poll-to-terminal.\n *\n * Long-running server work (e.g. extraction export) is exposed as an *async\n * operation*: a POST enqueues it and returns an envelope you poll via\n * `GET /api/async-operations/{id}` until terminal. Create / get / cancel all\n * return the same `AsyncOperationResponse` envelope, modeled as `AsyncOperation`.\n *\n * Terminal statuses are `succeeded` / `failed` / `cancelled` (double-L); the\n * non-terminal ones are `queued` / `running` (there is no \"pending\").\n * `AsyncOperationHandle.wait` polls with light backoff until terminal, rejecting\n * on `failed` / timeout. The clock and sleep are injectable so tests never wait.\n *\n * New to TS — the node-sdk has no waiter; ported from the Python reference.\n */\n\nimport { SpreadSpaceError } from '../errors.js';\nimport type { HttpMethod, Transport } from '../transport.js';\nimport type { RequestOptions } from '../types.js';\n\nconst OPERATIONS_PATH = '/api/async-operations';\nconst BACKOFF_CAP_MS = 10_000;\n\n/** Operation statuses considered terminal — `wait()` stops on these. */\nexport const TERMINAL_STATUSES = new Set(['succeeded', 'failed', 'cancelled']);\n\n/** Minimal transport surface this helper needs. `Transport.request` satisfies it. */\nexport type OperationsTransport = Pick<Transport, 'request'>;\n\n/** Mapped `AsyncOperationResponse` envelope. Raw body kept on `.raw` for forward-compat. */\nexport interface AsyncOperation {\n operationId: string;\n kind: string;\n status: string;\n progress: { completed?: number; total?: number };\n result: unknown;\n resultUrl?: string;\n resultExpiresAt?: string;\n errorCode?: string;\n errorMessage?: string;\n createdAt?: string;\n startedAt?: string;\n completedAt?: string;\n expiresAt?: string;\n links: { self?: string; cancel?: string };\n /** Full decoded body, for fields not yet mapped. */\n raw: Record<string, unknown>;\n}\n\n/** Body for `POST /api/async-operations/extraction_export`. All fields optional. */\nexport interface ExtractionExportParams {\n borrowerId?: string;\n loanId?: string;\n documentIds?: string[];\n format?: string;\n deliveryMode?: string;\n}\n\n/** Tuning + injected clock for `AsyncOperationHandle.wait`. */\nexport interface WaitOptions {\n /** Give up after this many ms (default 300000). Rejects with `AsyncOperationTimeout`. `null` = forever (parity with Python `timeout=None`). */\n timeoutMs?: number | null;\n /** Initial poll interval in ms (default 2000); backs off ×2 up to a 10s cap. */\n pollIntervalMs?: number;\n /** Sleep impl. Injectable so tests don't actually wait. Defaults to `setTimeout`. */\n sleep?: (ms: number) => Promise<void>;\n /** Monotonic clock in ms. Injectable for deterministic timeout tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** Map a decoded envelope to `AsyncOperation`, tolerating missing optional fields. */\nfunction toAsyncOperation(body: Record<string, unknown>): AsyncOperation {\n const b = body ?? {};\n return {\n operationId: b['operation_id'] as string,\n kind: b['kind'] as string,\n status: b['status'] as string,\n progress: (b['progress'] as AsyncOperation['progress']) ?? {},\n result: b['result'],\n resultUrl: b['result_url'] as string | undefined,\n resultExpiresAt: b['result_expires_at'] as string | undefined,\n errorCode: b['error_code'] as string | undefined,\n errorMessage: b['error_message'] as string | undefined,\n createdAt: b['created_at'] as string | undefined,\n startedAt: b['started_at'] as string | undefined,\n completedAt: b['completed_at'] as string | undefined,\n expiresAt: b['expires_at'] as string | undefined,\n links: (b['links'] as AsyncOperation['links']) ?? {},\n raw: b,\n };\n}\n\n/** True when the operation reached a terminal status. */\nexport function isTerminal(operation: AsyncOperation): boolean {\n return TERMINAL_STATUSES.has(operation.status);\n}\n\n/**\n * A waited-on operation ended in status `failed`. Carries the operation's\n * `error_code` (as `type`) / `error_message`, plus the terminal op on `.operation`.\n */\nexport class AsyncOperationError extends SpreadSpaceError {\n public readonly operation: AsyncOperation;\n\n constructor(operation: AsyncOperation) {\n super({\n type: operation.errorCode ?? 'operation_failed',\n message: operation.errorMessage ?? 'operation failed',\n statusCode: 0,\n });\n this.operation = operation;\n Object.setPrototypeOf(this, AsyncOperationError.prototype);\n }\n}\n\n/** `wait()` exceeded its timeout before the operation reached a terminal state. */\nexport class AsyncOperationTimeout extends SpreadSpaceError {\n public readonly operation: AsyncOperation;\n\n constructor(message: string, operation: AsyncOperation) {\n super({ type: 'operation_timeout', message, statusCode: 0 });\n this.operation = operation;\n Object.setPrototypeOf(this, AsyncOperationTimeout.prototype);\n }\n}\n\n/** Live handle to one async operation: refresh, cancel, wait-to-terminal. */\nexport class AsyncOperationHandle {\n private op: AsyncOperation;\n\n constructor(\n private readonly transport: OperationsTransport,\n operation: AsyncOperation,\n ) {\n this.op = operation;\n }\n\n get id(): string {\n return this.op.operationId;\n }\n\n /** Last-known state — does not hit the network. */\n get operation(): AsyncOperation {\n return this.op;\n }\n\n /** GET the operation once and cache the result. */\n async refresh(options?: RequestOptions): Promise<AsyncOperation> {\n this.op = await getOperation(this.transport, this.id, options);\n return this.op;\n }\n\n /** POST the cancel route and cache the returned state. */\n async cancel(options?: RequestOptions): Promise<AsyncOperation> {\n this.op = await cancelOperation(this.transport, this.id, options);\n return this.op;\n }\n\n /**\n * Poll until terminal. Resolves the `AsyncOperation` on `succeeded` /\n * `cancelled`; rejects with `AsyncOperationError` on `failed`, or\n * `AsyncOperationTimeout` if `timeoutMs` elapses while still non-terminal.\n * `sleep` and `now` are injectable so tests never actually wait.\n */\n async wait(options: WaitOptions = {}): Promise<AsyncOperation> {\n const timeoutMs = options.timeoutMs === undefined ? 300_000 : options.timeoutMs;\n const sleep = options.sleep ?? defaultSleep;\n const now = options.now ?? Date.now;\n // `null` disables the deadline — poll until terminal (parity with Python timeout=None).\n const deadline = timeoutMs === null ? null : now() + timeoutMs;\n let delay = options.pollIntervalMs ?? 2_000;\n\n for (;;) {\n const op = await this.refresh();\n if (isTerminal(op)) {\n if (op.status === 'failed') {\n throw new AsyncOperationError(op);\n }\n return op;\n }\n if (deadline !== null && now() >= deadline) {\n throw new AsyncOperationTimeout(\n `operation ${this.id} did not finish within ${timeoutMs}ms (last status ${JSON.stringify(op.status)})`,\n op,\n );\n }\n await sleep(delay);\n delay = Math.min(delay * 2, BACKOFF_CAP_MS);\n }\n }\n}\n\n/**\n * Enqueue an extraction export and return a handle to poll/cancel it. All\n * fields are optional; `undefined` values are omitted from the body rather than\n * sent as wire nulls.\n *\n * POST /api/async-operations/extraction_export\n */\nexport async function createExtractionExport(\n transport: OperationsTransport,\n params: ExtractionExportParams = {},\n options?: RequestOptions,\n): Promise<AsyncOperationHandle> {\n const body: Record<string, unknown> = {};\n if (params.borrowerId !== undefined) body['borrower_id'] = params.borrowerId;\n if (params.loanId !== undefined) body['loan_id'] = params.loanId;\n if (params.documentIds !== undefined) body['document_ids'] = params.documentIds;\n if (params.format !== undefined) body['format'] = params.format;\n if (params.deliveryMode !== undefined) body['delivery_mode'] = params.deliveryMode;\n\n const resp = await transport.request<Record<string, unknown>>(\n 'POST',\n `${OPERATIONS_PATH}/extraction_export`,\n { body, ...options },\n );\n return new AsyncOperationHandle(transport, toAsyncOperation(resp));\n}\n\n/**\n * Fetch one async operation by id.\n *\n * GET /api/async-operations/{operationId}\n */\nexport async function getOperation(\n transport: OperationsTransport,\n operationId: string,\n options?: RequestOptions,\n): Promise<AsyncOperation> {\n const resp = await transport.request<Record<string, unknown>>(\n 'GET',\n `${OPERATIONS_PATH}/${encodeURIComponent(operationId)}`,\n options,\n );\n return toAsyncOperation(resp);\n}\n\n/**\n * Request cancellation. Cancelling a terminal (non-cancelled) op → 409\n * (`ConflictError`); cancelling an already-cancelled op is idempotent (200).\n *\n * POST /api/async-operations/{operationId}/cancel\n */\nexport async function cancelOperation(\n transport: OperationsTransport,\n operationId: string,\n options?: RequestOptions,\n): Promise<AsyncOperation> {\n const resp = await transport.request<Record<string, unknown>>(\n 'POST',\n `${OPERATIONS_PATH}/${encodeURIComponent(operationId)}/cancel`,\n options,\n );\n return toAsyncOperation(resp);\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Re-exported so the client facade can route to the cancel verb without a deep import.\nexport type { HttpMethod };\n","/**\n * Document upload: mint presigned URL -> PUT bytes to S3 -> confirm -> poll.\n *\n * Three-step dance so raw bytes never transit a SpreadSpace endpoint body — they\n * go straight to S3:\n *\n * 1. `POST /api/documents/presigned-url` mints a short-lived V4-signed S3 URL\n * plus the `jobId` that tracks processing. The response is **camelCase** (the\n * DTO carries no `[JsonPropertyName]`); `parsePresignedUrl` reads camelCase\n * and falls back to snake_case defensively. This endpoint can also return\n * **402** (`SubscriptionRequiredResponse`, a non-canonical `{error,message}`\n * body) when billing is inactive — the transport raises; we let it propagate\n * so callers see the billing wall rather than a hang.\n * 2. HTTP `PUT` the raw bytes to that URL. The `Content-Type` MUST equal the one\n * sent in step 1 — it is baked into the V4 signature, so a mismatch is a 403.\n * 3. `POST /api/documents/{jobId}/confirm-upload` kicks off the Step Functions\n * pipeline; the job goes `PROCESSING`.\n *\n * `JobHandle.wait` then polls `GET /api/documents/{jobId}/status` to a terminal\n * job status. Terminal job statuses are `COMPLETED` / `FAILED` (per the API's\n * `JobsController.IsTerminalStatus`); `PENDING` / `PROCESSING` are in-flight.\n * (`REJECTED` is a per-*document* outcome, not a job status — a FAILED job is what\n * surfaces as \"rejected\" in the UI.)\n */\n\nimport { createHash } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport { SpreadSpaceError } from '../errors.js';\nimport type { Transport } from '../transport.js';\n\n// Real terminal job-level statuses (api/src/Controllers/JobsController.cs\n// IsTerminalStatus: COMPLETED || FAILED). Callers may override via wait(...).\nexport const TERMINAL_JOB_STATUSES: ReadonlySet<string> = new Set(['COMPLETED', 'FAILED']);\n\n// Statuses that mean the job finished badly — wait() raises on these.\nconst FAILURE_JOB_STATUSES: ReadonlySet<string> = new Set(['FAILED']);\n\nconst DOCUMENTS_PATH = '/api/documents';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\n\n/** A minted upload target (`PresignedUrlResponse`). */\nexport interface PresignedUrl {\n jobId: string;\n uploadUrl: string;\n s3Key: string;\n expiresInSeconds?: number;\n}\n\n/**\n * Parse the presigned-url response, tolerating camelCase or snake_case keys.\n *\n * The live DTO is camelCase (`jobId`, `uploadUrl`, ...); we read those first and\n * fall back to snake_case so the helper survives a future wire rename.\n */\nexport function parsePresignedUrl(body: Record<string, unknown>): PresignedUrl {\n const pick = (camel: string, snake: string): unknown => {\n const value = body[camel];\n return value === undefined || value === null ? body[snake] : value;\n };\n\n return {\n jobId: pick('jobId', 'job_id') as string,\n uploadUrl: pick('uploadUrl', 'upload_url') as string,\n s3Key: pick('s3Key', 's3_key') as string,\n expiresInSeconds: pick('expiresInSeconds', 'expires_in_seconds') as number | undefined,\n };\n}\n\n/**\n * Upload failed, or the job reached a terminal failure status. Carries the\n * last-known status body on `.statusBody` when a waited-on job ended `FAILED`.\n */\nexport class UploadError extends SpreadSpaceError {\n public readonly statusBody?: Record<string, unknown>;\n\n constructor(message: string, statusBody?: Record<string, unknown>) {\n super({ type: 'upload_failed', message, statusCode: 0 });\n this.statusBody = statusBody;\n Object.setPrototypeOf(this, UploadError.prototype);\n }\n}\n\n/** `wait()` exceeded its timeout before the job reached a terminal status. */\nexport class UploadTimeout extends SpreadSpaceError {\n public readonly statusBody?: Record<string, unknown>;\n\n constructor(message: string, statusBody?: Record<string, unknown>) {\n super({ type: 'upload_timeout', message, statusCode: 0 });\n this.statusBody = statusBody;\n Object.setPrototypeOf(this, UploadTimeout.prototype);\n }\n}\n\n/** Options for {@link JobHandle.wait}. Clock and sleep are injectable for tests. */\nexport interface WaitOptions {\n /** Overall budget in ms before raising {@link UploadTimeout}. Default 600000. `null` = forever. */\n timeoutMs?: number | null;\n /** Delay between status polls, in ms. Default 3000. */\n pollIntervalMs?: number;\n /** Terminal statuses that end the poll. Default {@link TERMINAL_JOB_STATUSES}. */\n terminalStatuses?: ReadonlySet<string>;\n /** Sleep used between polls. Injectable so tests don't actually wait. */\n sleep?: (ms: number) => Promise<void>;\n /** Monotonic clock (ms). Injectable so tests don't actually wait. */\n now?: () => number;\n}\n\n/** Live handle to one upload job: fetch status, wait-to-terminal. */\nexport class JobHandle {\n private status_?: string;\n private raw: Record<string, unknown>;\n\n constructor(\n private readonly transport: Transport,\n public readonly id: string,\n status?: string,\n raw?: Record<string, unknown>,\n ) {\n this.status_ = status;\n this.raw = raw ?? {};\n }\n\n /** GET the status route once; cache and return the decoded body. */\n async status(): Promise<Record<string, unknown>> {\n const body =\n (await this.transport.request<Record<string, unknown>>(\n 'GET',\n `${DOCUMENTS_PATH}/${encodeURIComponent(this.id)}/status`,\n )) ?? {};\n this.raw = body;\n if (typeof body.status === 'string') this.status_ = body.status;\n return body;\n }\n\n /**\n * Poll status until terminal; return the final status body.\n *\n * Raises {@link UploadError} if the job ends in a failure status (`FAILED`),\n * {@link UploadTimeout} if `timeoutMs` elapses while the job is still\n * non-terminal. Clock and sleep are injectable so tests never actually wait.\n */\n async wait(options: WaitOptions = {}): Promise<Record<string, unknown>> {\n const {\n timeoutMs = 600_000,\n pollIntervalMs = 3_000,\n terminalStatuses = TERMINAL_JOB_STATUSES,\n sleep = defaultSleep,\n now = defaultNow,\n } = options;\n\n const deadline = timeoutMs === null ? null : now() + timeoutMs;\n for (;;) {\n const body = await this.status();\n const current = typeof body.status === 'string' ? body.status : undefined;\n if (current !== undefined && terminalStatuses.has(current)) {\n if (FAILURE_JOB_STATUSES.has(current)) {\n throw new UploadError(`job ${this.id} ended in status ${current}`, body);\n }\n return body;\n }\n if (deadline !== null && now() >= deadline) {\n throw new UploadTimeout(\n `job ${this.id} did not finish within ${timeoutMs}ms (last status ${String(current)})`,\n body,\n );\n }\n await sleep(pollIntervalMs);\n }\n }\n}\n\n/**\n * Accepted upload sources. Node and web friendly:\n * - `string` path -> read via `node:fs`, derive name + content-type\n * - `Uint8Array` / `Buffer` -> `fileName` + `contentType` required\n * - `Blob` / `File` -> uses `.name` / `.type` when present\n * - `Readable` (or any async/sync iterable of chunks) -> collected to bytes;\n * `fileName` + `contentType` required\n */\nexport type UploadSource =\n | string\n | Uint8Array\n | Blob\n | NodeJS.ReadableStream\n | AsyncIterable<Uint8Array | string>\n | Iterable<Uint8Array | string>;\n\n/** Options for {@link uploadDocument}. */\nexport interface UploadOptions {\n /** Overrides the derived file name. Required for raw bytes / streams. <=255 chars. */\n fileName?: string;\n /** Overrides the derived content type. Required for raw bytes / streams. */\n contentType?: string;\n /** Byte length sent to the server. Defaults to the resolved payload length. */\n fileSize?: number;\n /** Associate the upload with a borrower. */\n borrowerId?: string;\n /** Associate the upload with a loan. */\n loanId?: string;\n /** sha256 hex digest for server-side dedup. Computed from bytes when omitted. */\n contentHash?: string;\n /** Compute + send a sha256 digest when `contentHash` is absent. Default true. */\n computeHash?: boolean;\n /** Marks the upload as a sample document in confirm-upload. */\n sampleDocument?: string;\n /** Poll to a terminal job status before returning. Default false. */\n wait?: boolean;\n /** Forwarded to {@link JobHandle.wait} when `wait` is true. */\n waitOptions?: WaitOptions;\n /** `AbortSignal` propagated to every API + S3 request. */\n signal?: AbortSignal;\n}\n\n// Concrete `ArrayBuffer`-backed bytes. A bare `Uint8Array` widens to\n// `Uint8Array<ArrayBufferLike>`, which the DOM `BodyInit` union rejects under\n// strict types; pinning the backing buffer keeps the S3 PUT type-clean.\ntype Bytes = Uint8Array<ArrayBuffer>;\n\ninterface ResolvedPayload {\n bytes: Bytes;\n fileName: string;\n contentType: string;\n}\n\n/**\n * Upload a document: presigned-url -> S3 PUT -> confirm-upload.\n *\n * `file` may be a path string, raw `Uint8Array`/`Buffer`, a `Blob`/`File`, or a\n * `Readable`/iterable of chunks. For raw bytes and streams, `fileName` and\n * `contentType` are required; otherwise they are derived from the path /\n * `Blob.name` + a small extension map.\n *\n * When `computeHash` is true and no `contentHash` is given, a sha256 hex digest of\n * the bytes is computed and sent (the server uses it for dedup). `fileSize`\n * defaults to the byte length.\n *\n * Returns a {@link JobHandle} for the `PROCESSING` job. With `wait: true`, also\n * polls to a terminal job status before returning.\n *\n * Throws: `TypeError` for an unsupported `file`; `Error` when a required\n * `fileName`/`contentType` is missing; whatever the transport raises on a non-2xx\n * — e.g. the **402** billing wall on `presigned-url`.\n */\nexport async function uploadDocument(\n transport: Transport,\n file: UploadSource,\n options: UploadOptions = {},\n): Promise<JobHandle> {\n const {\n fileName,\n contentType,\n fileSize,\n borrowerId,\n loanId,\n contentHash,\n computeHash = true,\n sampleDocument,\n wait = false,\n waitOptions,\n signal,\n } = options;\n\n const payload = await resolvePayload(file, { fileName, contentType });\n if (payload.fileName.length > 255) {\n throw new Error('fileName must be <= 255 characters.');\n }\n\n const size = fileSize ?? payload.bytes.length;\n let digest = contentHash;\n if (digest === undefined && computeHash) {\n digest = createHash('sha256').update(payload.bytes).digest('hex');\n }\n\n // Step 1: mint the presigned URL (camelCase response; 402 if billing inactive).\n // Omit null/undefined fields from the body.\n const presignBody: Record<string, unknown> = { file_name: payload.fileName, content_type: payload.contentType };\n if (size !== undefined) presignBody.file_size = size;\n if (borrowerId !== undefined) presignBody.borrower_id = borrowerId;\n if (loanId !== undefined) presignBody.loan_id = loanId;\n if (digest !== undefined) presignBody.content_hash = digest;\n\n const presignResponse = await transport.request<Record<string, unknown>>(\n 'POST',\n `${DOCUMENTS_PATH}/presigned-url`,\n { body: presignBody, signal },\n );\n const presigned = parsePresignedUrl(presignResponse ?? {});\n\n // Step 2: PUT raw bytes straight to S3. Content-Type MUST match step 1's.\n await transport.putPresigned(presigned.uploadUrl, payload.bytes, payload.contentType, { signal });\n\n // Step 3: confirm -> Step Functions pipeline starts; job goes PROCESSING.\n const confirmBody = sampleDocument !== undefined ? { sample_document: sampleDocument } : {};\n const confirmed =\n (await transport.request<Record<string, unknown>>(\n 'POST',\n `${DOCUMENTS_PATH}/${encodeURIComponent(presigned.jobId)}/confirm-upload`,\n { body: confirmBody, signal },\n )) ?? {};\n\n const handle = new JobHandle(\n transport,\n typeof confirmed.jobId === 'string' ? confirmed.jobId : presigned.jobId,\n typeof confirmed.status === 'string' ? confirmed.status : undefined,\n confirmed,\n );\n if (wait) {\n await handle.wait(waitOptions);\n }\n return handle;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Helpers — module-private; not exported.\n// ──────────────────────────────────────────────────────────────────────────\n\nfunction defaultSleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction defaultNow(): number {\n return Date.now();\n}\n\n/** Normalize `file` into bytes + fileName + contentType. */\nasync function resolvePayload(\n file: UploadSource,\n opts: { fileName?: string; contentType?: string },\n): Promise<ResolvedPayload> {\n // (a) path string -> read bytes, derive name + content type.\n if (typeof file === 'string') {\n const bytes = toOwnedBytes(await readFile(file));\n const name = opts.fileName ?? basename(file);\n const ctype = opts.contentType ?? guessContentType(name) ?? DEFAULT_CONTENT_TYPE;\n return { bytes, fileName: name, contentType: ctype };\n }\n\n // (b) Blob / File -> read bytes, prefer .name / .type.\n if (isBlob(file)) {\n const bytes = toOwnedBytes(new Uint8Array(await file.arrayBuffer()));\n const blobName = typeof (file as File).name === 'string' ? (file as File).name : '';\n const name = opts.fileName ?? blobName;\n if (!name) {\n throw new Error('fileName is required for a Blob without a name.');\n }\n const ctype =\n opts.contentType ?? (file.type || undefined) ?? guessContentType(name) ?? DEFAULT_CONTENT_TYPE;\n return { bytes, fileName: name, contentType: ctype };\n }\n\n // (c) raw bytes -> name + content type must be supplied.\n if (isBinaryData(file)) {\n if (!opts.fileName || !opts.contentType) {\n throw new Error('fileName and contentType are required when uploading raw bytes.');\n }\n return { bytes: toOwnedBytes(file), fileName: opts.fileName, contentType: opts.contentType };\n }\n\n // (d) Readable / iterable of chunks -> collect, name + content type required.\n if (isAsyncIterable(file) || isIterable(file)) {\n if (!opts.fileName || !opts.contentType) {\n throw new Error('fileName and contentType are required when uploading a stream.');\n }\n const bytes = await collectChunks(file);\n return { bytes, fileName: opts.fileName, contentType: opts.contentType };\n }\n\n throw new TypeError(\n 'file must be a path string, Uint8Array/Buffer, Blob/File, or a Readable/iterable of chunks.',\n );\n}\n\nfunction isBlob(value: unknown): value is Blob {\n return (\n typeof Blob !== 'undefined' &&\n value instanceof Blob &&\n typeof (value as Blob).arrayBuffer === 'function'\n );\n}\n\nfunction isBinaryData(value: unknown): value is Uint8Array | ArrayBuffer {\n return value instanceof Uint8Array || value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\n/**\n * Copy any binary input into a fresh `ArrayBuffer`-backed `Uint8Array`. The copy\n * (a) pins the backing-buffer type so the value satisfies `BodyInit`, and (b)\n * snapshots the bytes so a later mutation of the caller's buffer can't change\n * what we PUT.\n */\nfunction toOwnedBytes(value: Uint8Array | ArrayBuffer | ArrayBufferView): Bytes {\n const src =\n value instanceof Uint8Array\n ? value\n : value instanceof ArrayBuffer\n ? new Uint8Array(value)\n : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n const out = new Uint8Array(new ArrayBuffer(src.byteLength));\n out.set(src);\n return out;\n}\n\nfunction isAsyncIterable(value: unknown): value is AsyncIterable<Uint8Array | string> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function'\n );\n}\n\nfunction isIterable(value: unknown): value is Iterable<Uint8Array | string> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Iterable<unknown>)[Symbol.iterator] === 'function'\n );\n}\n\n/** Collect a Readable/iterable of chunks (bytes or utf-8 strings) into one buffer. */\nasync function collectChunks(\n source: AsyncIterable<Uint8Array | string> | Iterable<Uint8Array | string>,\n): Promise<Bytes> {\n const parts: Uint8Array[] = [];\n let total = 0;\n for await (const chunk of source as AsyncIterable<Uint8Array | string>) {\n const part = typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk;\n parts.push(part);\n total += part.byteLength;\n }\n const out = new Uint8Array(new ArrayBuffer(total));\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.byteLength;\n }\n return out;\n}\n\n// Minimal extension -> MIME map (no `mime` dependency). Covers the doc types the\n// pipeline ingests; anything else falls back to application/octet-stream.\nconst CONTENT_TYPE_BY_EXT: Record<string, string> = {\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n tif: 'image/tiff',\n tiff: 'image/tiff',\n csv: 'text/csv',\n txt: 'text/plain',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n};\n\nfunction guessContentType(name: string): string | undefined {\n const dot = name.lastIndexOf('.');\n if (dot < 0 || dot === name.length - 1) return undefined;\n return CONTENT_TYPE_BY_EXT[name.slice(dot + 1).toLowerCase()];\n}\n","/**\n * Webhook signature verification + typed event envelopes.\n *\n * Byte-for-byte compatible with the server signer `api/src/Webhooks/WebhookSigner.cs`.\n * Wire format mirrors Stripe's `Stripe-Signature`:\n *\n * SpreadSpace-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256(secret, \"{t}.{body}\")>\n *\n * The verifier:\n * 1. Parses `t=` and `v1=` tokens (tolerant of extra/reordered/unknown pairs to\n * stay forward-compatible with future schemes).\n * 2. Recomputes HMAC-SHA256 over `\"{timestamp}.{rawBody}\"` with the secret.\n * 3. Compares with `crypto.timingSafeEqual` on decoded bytes — never on hex\n * strings (`===` short-circuits and leaks prefix info via timing).\n * 4. Two-sided freshness check: rejects timestamps too far in the past (replay\n * protection) AND too far in the future (attacker-controlled skew).\n *\n * Verification order matches the C# side: HMAC FIRST, then freshness. That way a\n * forged-but-fresh request and a real-but-stale request fail indistinguishably.\n *\n * Tree-shakeable: importable as `@spreadspace/sdk/webhooks` so a webhook-receiver\n * Lambda pulls in only the verifier, not the whole client + resource tree.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** Default freshness tolerance — matches `WebhookSignatureFormat.DefaultFreshnessTolerance` (300s). */\nconst DEFAULT_FRESHNESS_TOLERANCE_SECONDS = 5 * 60;\n\n/** Wire constant for the v1 HMAC-SHA256 scheme. */\nconst VERSION_1_PREFIX = 'v1';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Webhook event types — discriminated union over the five canonical events.\n//\n// Mirror the C# payload records under `api/src/Webhooks/Events/`. Field names\n// are the exact wire keys (snake_case).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonical webhook event-type identifier strings. Stable wire constants stored\n * verbatim in the envelope `type` field. `*` is a subscription wildcard, never\n * an emitted `type`, so it is deliberately excluded from this union.\n */\nexport type WebhookEventType =\n | 'document.processed'\n | 'document.failed'\n | 'extraction.ready'\n | 'job.completed'\n | 'loan.classified';\n\n/** `document.processed` payload. `extraction_id === document_id` if an extraction phase ran, `null` if classified-only. */\nexport interface DocumentProcessedPayload {\n document_id: string;\n job_id: string;\n borrower_id: string;\n loan_id: string;\n document_type: string;\n processed_at: string;\n extraction_id?: string | null;\n}\n\n/** `document.failed` payload. `reason` mirrors `ExtractedDocument.ErrorMessage`. */\nexport interface DocumentFailedPayload {\n document_id: string;\n job_id: string;\n borrower_id: string;\n loan_id: string;\n reason: string;\n failed_at: string;\n}\n\n/** `extraction.ready` payload. Fired once per doc when extracted line items become queryable. */\nexport interface ExtractionReadyPayload {\n extraction_id: string;\n document_id: string;\n job_id: string;\n borrower_id: string;\n loan_id: string;\n document_type: string;\n ready_at: string;\n}\n\n/** `job.completed` payload. Terminal package event, fired once every doc reaches a terminal state. */\nexport interface JobCompletedPayload {\n job_id: string;\n borrower_id: string;\n loan_id: string;\n document_count_total: number;\n document_count_succeeded: number;\n document_count_failed: number;\n completed_at: string;\n}\n\n/** `loan.classified` payload. Fired once per loan after the classification cascade finishes. */\nexport interface LoanClassifiedPayload {\n loan_id: string;\n borrower_id: string;\n job_id: string;\n document_count: number;\n classified_at: string;\n}\n\n/**\n * Common envelope fields wrapping every event (the bytes that are signed).\n * Property order is pinned server-side via `JsonPropertyOrder`; the signature is\n * over the raw byte stream, so order is immaterial to the verifier but the\n * fields are documented here for completeness. `livemode` is `false` for the\n * sandbox simulator, `true` otherwise.\n */\ninterface WebhookEnvelopeBase {\n id: string;\n created: number;\n tenant_id: string;\n livemode: boolean;\n}\n\n/**\n * Discriminated union over the five canonical webhook events. Switch on `type`\n * to narrow `data` to the matching payload.\n */\nexport type WebhookEvent =\n | (WebhookEnvelopeBase & { type: 'document.processed'; data: DocumentProcessedPayload })\n | (WebhookEnvelopeBase & { type: 'document.failed'; data: DocumentFailedPayload })\n | (WebhookEnvelopeBase & { type: 'extraction.ready'; data: ExtractionReadyPayload })\n | (WebhookEnvelopeBase & { type: 'job.completed'; data: JobCompletedPayload })\n | (WebhookEnvelopeBase & { type: 'loan.classified'; data: LoanClassifiedPayload });\n\n/** Parsed envelope returned by {@link verifyAndParseWebhook}, narrowed on `type`. */\nexport type VerifyAndParseResult = WebhookEvent;\n\n/** Options for {@link verifyWebhook} / {@link verifyAndParseWebhook}. */\nexport interface VerifyWebhookOptions {\n /**\n * Maximum age (and maximum future skew) of the timestamp before rejection.\n * Symmetric — `Math.abs(now - t)` must be ≤ this value. Default 300 seconds.\n */\n freshnessTolerance?: number;\n\n /**\n * Override the \"current time\" (unix seconds) used for the freshness check.\n * Useful in tests; otherwise leave undefined and the verifier reads\n * `Date.now() / 1000`.\n */\n currentTimestamp?: number;\n}\n\n/**\n * Thrown when a webhook signature fails verification or the envelope is\n * structurally malformed. Self-contained — distinct from `SpreadSpaceError`\n * because verifiers don't need the full request/response error machinery.\n */\nexport class WebhookSignatureError extends Error {\n public readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = 'WebhookSignatureError';\n this.cause = cause;\n Object.setPrototypeOf(this, WebhookSignatureError.prototype);\n }\n}\n\n/**\n * Verify a `SpreadSpace-Signature` header against a body and signing secret.\n * Throws {@link WebhookSignatureError} on any failure (malformed header, bad\n * HMAC, stale timestamp, wrong secret); returns `true` on success.\n *\n * @param rawBody The exact bytes received on the wire — NOT re-serialized JSON.\n * Any byte-level transformation (re-stringify, gzip decode, encoding change)\n * invalidates the signature.\n * @param signature Contents of the `SpreadSpace-Signature` HTTP header.\n * @param secret The plaintext signing secret (`whsec_...`). Never log this.\n * @param options Optional freshness window + clock injection.\n */\nexport function verifyWebhook(\n rawBody: string | Buffer | Uint8Array,\n signature: string,\n secret: string,\n options?: VerifyWebhookOptions,\n): true {\n if (typeof signature !== 'string' || signature.length === 0) {\n throw new WebhookSignatureError('SpreadSpace-Signature header is missing or empty.');\n }\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new WebhookSignatureError('Webhook signing secret is missing or empty.');\n }\n if (rawBody === null || rawBody === undefined) {\n throw new WebhookSignatureError('Webhook raw body is missing.');\n }\n\n const parsed = parseSignatureHeader(signature);\n if (!parsed) {\n throw new WebhookSignatureError('Webhook signature header is malformed.');\n }\n const { timestamp, hex } = parsed;\n\n // HMAC FIRST. Never leak (via timing or distinct error text) whether a request\n // was a forged signature vs. a real one that aged out — both fail the same way.\n const bodyString = bodyToString(rawBody);\n const expectedHex = computeHmacHex(secret, `${timestamp}.${bodyString}`);\n\n if (!hexEquals(expectedHex, hex)) {\n throw new WebhookSignatureError('Webhook signature does not match.');\n }\n\n // Then freshness. Two-sided: too old OR too far in the future.\n const tolerance = options?.freshnessTolerance ?? DEFAULT_FRESHNESS_TOLERANCE_SECONDS;\n const now = options?.currentTimestamp ?? Math.floor(Date.now() / 1000);\n const age = now - timestamp;\n if (age > tolerance || age < -tolerance) {\n throw new WebhookSignatureError(\n `Webhook timestamp is outside the freshness window of ${tolerance}s.`,\n );\n }\n\n return true;\n}\n\n/**\n * Verify a webhook signature and JSON-parse the body into a typed\n * {@link WebhookEvent} envelope. Throws {@link WebhookSignatureError} on\n * signature failure or invalid JSON. Switch on `event.type` to narrow\n * `event.data` to the matching payload.\n */\nexport function verifyAndParseWebhook(\n rawBody: string | Buffer | Uint8Array,\n signature: string,\n secret: string,\n options?: VerifyWebhookOptions,\n): VerifyAndParseResult {\n verifyWebhook(rawBody, signature, secret, options);\n\n const bodyString = bodyToString(rawBody);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyString);\n } catch (cause) {\n throw new WebhookSignatureError('Webhook body is not valid JSON.', cause);\n }\n\n if (!isPlainObject(parsed)) {\n throw new WebhookSignatureError('Webhook body is not a JSON object.');\n }\n const candidate = parsed;\n if (typeof candidate.type !== 'string' || candidate.type.length === 0) {\n throw new WebhookSignatureError('Webhook body is missing a `type` field.');\n }\n if (typeof candidate.id !== 'string' || candidate.id.length === 0) {\n throw new WebhookSignatureError('Webhook body is missing an `id` field.');\n }\n if (typeof candidate.created !== 'number') {\n throw new WebhookSignatureError('Webhook body is missing a numeric `created` field.');\n }\n if (typeof candidate.tenant_id !== 'string' || candidate.tenant_id.length === 0) {\n throw new WebhookSignatureError('Webhook body is missing a `tenant_id` field.');\n }\n if (!isPlainObject(candidate.data)) {\n throw new WebhookSignatureError('Webhook body is missing a `data` object.');\n }\n\n return parsed as unknown as VerifyAndParseResult;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Internals\n// ────────────────────────────────────────────────────────────────────────────\n\ninterface ParsedSignature {\n timestamp: number;\n hex: string;\n}\n\n/**\n * Parse a header in the form `t=<long>,v1=<hex>`. Tolerant of whitespace between\n * segments and of additional unrecognized pairs (e.g. a future `v2=...`) — those\n * are silently ignored, keeping the verifier forward-compatible. Duplicate `t=`\n * or `v1=`, an empty value, a missing `=`, or fewer than two segments → reject.\n */\nfunction parseSignatureHeader(header: string): ParsedSignature | null {\n const segments = header.split(',').filter((s) => s.length > 0);\n if (segments.length < 2) return null;\n\n let parsedTs: number | null = null;\n let parsedV1: string | null = null;\n\n for (const raw of segments) {\n const segment = raw.trim();\n const eq = segment.indexOf('=');\n if (eq <= 0 || eq === segment.length - 1) {\n // Missing key, missing value, or `=` at the very end (`v1=`).\n return null;\n }\n\n const key = segment.slice(0, eq).trim();\n const value = segment.slice(eq + 1).trim();\n\n if (key === 't') {\n if (parsedTs !== null) return null; // Duplicate `t=`.\n // Strict integer parse — reject `1.5`, `1e3`, leading `+`, etc. The C#\n // verifier uses `long.TryParse(NumberStyles.Integer, Invariant)`, which\n // accepts an optional leading `-` and digits only. Mirror that.\n if (!/^-?\\d+$/.test(value)) return null;\n const ts = Number(value);\n if (!Number.isFinite(ts) || !Number.isInteger(ts)) return null;\n parsedTs = ts;\n } else if (key === VERSION_1_PREFIX) {\n if (parsedV1 !== null) return null; // Duplicate `v1=`.\n if (value.length === 0) return null;\n parsedV1 = value;\n }\n // Any other key — silently ignored for forward compatibility.\n }\n\n if (parsedTs === null || parsedV1 === null) return null;\n return { timestamp: parsedTs, hex: parsedV1 };\n}\n\n/**\n * Compute lowercase hex HMAC-SHA256 digest. Matches the C# side's\n * `Convert.ToHexStringLower(hmac.ComputeHash(...))` exactly — both UTF-8 encode\n * the key and message.\n */\nfunction computeHmacHex(secret: string, signedPayload: string): string {\n const hmac = createHmac('sha256', Buffer.from(secret, 'utf-8'));\n hmac.update(Buffer.from(signedPayload, 'utf-8'));\n return hmac.digest('hex'); // Node default is lowercase.\n}\n\n/**\n * Constant-time compare of two hex strings. Decode both sides to bytes before\n * `timingSafeEqual` (it requires equal-length inputs). A length mismatch fails\n * fast — length is public (SHA-256 emits 64 hex chars), not timing-sensitive.\n */\nfunction hexEquals(expectedHex: string, providedHex: string): boolean {\n if (expectedHex.length !== providedHex.length) return false;\n\n let expectedBytes: Buffer;\n let providedBytes: Buffer;\n try {\n expectedBytes = Buffer.from(expectedHex, 'hex');\n providedBytes = Buffer.from(providedHex, 'hex');\n } catch {\n return false;\n }\n\n // `Buffer.from(..., 'hex')` silently stops at the first non-hex character,\n // producing a shorter buffer — guard explicitly so invalid hex that truncates\n // to a matching length can't produce a false equality.\n if (expectedBytes.length !== providedBytes.length) return false;\n if (expectedBytes.length * 2 !== expectedHex.length) return false;\n if (providedBytes.length * 2 !== providedHex.length) return false;\n\n return timingSafeEqual(\n new Uint8Array(expectedBytes.buffer, expectedBytes.byteOffset, expectedBytes.byteLength),\n new Uint8Array(providedBytes.buffer, providedBytes.byteOffset, providedBytes.byteLength),\n );\n}\n\nfunction bodyToString(body: string | Buffer | Uint8Array): string {\n if (typeof body === 'string') return body;\n if (Buffer.isBuffer(body)) return body.toString('utf-8');\n return Buffer.from(body).toString('utf-8');\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n","/**\n * `client.webhooks` — webhook endpoint registration management plus the\n * signature-verification helpers surfaced as static methods for discoverability.\n *\n * The verification helpers are also exported as bare functions from\n * `@spreadspace/sdk/webhooks` for tree-shakeable import in webhook-receiver\n * Lambdas (where the full client would otherwise pull in the resource layer).\n */\n\nimport type { Transport } from '../transport.js';\nimport type { HttpMethod } from '../transport.js';\nimport type { InternalRequestOptions, RequestOptions } from '../types.js';\nimport { paginate } from '../helpers/pagination.js';\nimport type { AsyncPager } from '../helpers/pagination.js';\nimport {\n verifyAndParseWebhook as verifyAndParseWebhookFn,\n verifyWebhook as verifyWebhookFn,\n} from '../webhooks.js';\n\n/** Filters for {@link WebhooksResource.list} / {@link WebhooksResource.deliveries}. */\nexport interface WebhookListParams {\n /** Page size hint forwarded to the server. */\n limit?: number;\n /** Override the `SpreadSpace-Version` header for these page requests. */\n apiVersion?: string;\n}\n\n/** `client.webhooks` — manage webhook endpoints (`/api/admin/webhooks/*`). */\nexport class WebhooksResource {\n /**\n * Verify a `SpreadSpace-Signature` header against a body and signing secret.\n * Throws `WebhookSignatureError` on any failure.\n */\n public static readonly verifySignature = verifyWebhookFn;\n\n /**\n * Verify a webhook signature and JSON-parse the body into a typed\n * `WebhookEvent`. Throws `WebhookSignatureError` on signature failure or\n * invalid JSON.\n */\n public static readonly verifyAndParse = verifyAndParseWebhookFn;\n\n constructor(private readonly transport: Transport) {}\n\n /** Low-level request shim onto the shared transport. */\n private request<T>(method: HttpMethod, path: string, options?: InternalRequestOptions): Promise<T> {\n return this.transport.request<T>(method, path, options ?? {});\n }\n\n /**\n * List configured webhook endpoints for this organization.\n *\n * GET /api/admin/webhooks\n */\n list<T = Record<string, unknown>>(params: WebhookListParams = {}): AsyncPager<T> {\n return paginate<T>(this.transport, '/api/admin/webhooks', {\n params: { limit: params.limit },\n apiVersion: params.apiVersion,\n });\n }\n\n /**\n * Retrieve a single webhook endpoint.\n *\n * GET /api/admin/webhooks/{id}\n */\n retrieve<T = unknown>(endpointId: string, options?: RequestOptions): Promise<T> {\n return this.request<T>(\n 'GET',\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}`,\n options,\n );\n }\n\n /**\n * Create a new webhook endpoint. The response carries the plaintext signing\n * secret exactly once — store it server-side, do not log it.\n *\n * POST /api/admin/webhooks\n */\n create<T = unknown>(params: Record<string, unknown>, options?: RequestOptions): Promise<T> {\n return this.request<T>('POST', '/api/admin/webhooks', { body: params, ...options });\n }\n\n /**\n * Update endpoint metadata (URL, subscribed event types, enabled flag).\n *\n * PATCH /api/admin/webhooks/{id}\n */\n update<T = unknown>(\n endpointId: string,\n params: Record<string, unknown>,\n options?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(\n 'PATCH',\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}`,\n { body: params, ...options },\n );\n }\n\n /**\n * Delete a webhook endpoint.\n *\n * DELETE /api/admin/webhooks/{id}\n */\n delete<T = unknown>(endpointId: string, options?: RequestOptions): Promise<T> {\n return this.request<T>(\n 'DELETE',\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}`,\n options,\n );\n }\n\n /**\n * Rotate an endpoint's signing secret. The old secret stays valid for a grace\n * window so in-flight deliveries aren't dropped. Returns the new plaintext\n * secret exactly once.\n *\n * POST /api/admin/webhooks/{id}/rotate\n */\n rotateSecret<T = unknown>(\n endpointId: string,\n params: Record<string, unknown> = {},\n options?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(\n 'POST',\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}/rotate`,\n { body: params, ...options },\n );\n }\n\n /**\n * Iterate delivery attempts for an endpoint.\n *\n * GET /api/admin/webhooks/{id}/deliveries\n */\n deliveries<T = Record<string, unknown>>(\n endpointId: string,\n params: WebhookListParams = {},\n ): AsyncPager<T> {\n return paginate<T>(\n this.transport,\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}/deliveries`,\n { params: { limit: params.limit }, apiVersion: params.apiVersion },\n );\n }\n\n /**\n * Manually replay a delivery (e.g. after fixing a downstream outage).\n *\n * POST /api/admin/webhooks/{id}/deliveries/{deliveryId}/replay\n */\n replayDelivery<T = unknown>(\n endpointId: string,\n deliveryId: string,\n params: Record<string, unknown> = {},\n options?: RequestOptions,\n ): Promise<T> {\n return this.request<T>(\n 'POST',\n `/api/admin/webhooks/${encodeURIComponent(endpointId)}/deliveries/${encodeURIComponent(deliveryId)}/replay`,\n { body: params, ...options },\n );\n }\n}\n","/**\n * `client.embed` — mint short-lived embed credentials scoped to a single loan.\n *\n * Mint server-side with the integrator's `embed:write` API key, then hand the\n * resulting `embed_token` (or signed iframe URL) to the browser to bootstrap the\n * SpreadSpace review widget. The minted `ss_embed_` token is capped to a closed\n * read-only allowlist (`documents:read`, `extractions:read`, `spreads:read`) and\n * is locked to its `loan_id` — it cannot reach other loans or mint further tokens.\n *\n * Wire is snake_case throughout. Source of truth:\n * `api/src/Controllers/EmbedSessionsController.cs`.\n */\n\nimport type { Transport } from '../transport.js';\nimport type { HttpMethod } from '../transport.js';\nimport type { InternalRequestOptions, RequestOptions } from '../types.js';\n\n/** Body for {@link EmbedSessionsResource.create}. */\nexport interface CreateEmbedSessionParams {\n /** The loan the minted token is locked to. Required. */\n loan_id: string;\n /** Subset of the minting key's scopes; capped to the embed allowlist. Omit to inherit. */\n scopes?: string[];\n /** Token lifetime. Default 3600 (1h); must be > 0; cap 86400 (24h). */\n expires_in_seconds?: number;\n}\n\n/** `POST /api/embed/sessions` response shape. */\nexport interface EmbedSessionResult {\n /** The `ss_embed_*` bearer — shown ONCE, no retrieval endpoint. */\n embed_token: string;\n session_id: string;\n /** RFC3339 token expiry. */\n expires_at: string;\n loan_id: string;\n borrower_id: string;\n /** The granted scope subset. */\n scopes: string[];\n}\n\n/** Body for {@link EmbedIframeUrlsResource.create}. */\nexport interface CreateEmbedIframeUrlParams {\n /** The loan the handle/token is locked to. Required. */\n loan_id: string;\n /** Embed surface key (e.g. `\"spreading\"`), validated against the server allowlist. Required. */\n surface: string;\n /** Subset of the minting key's scopes; capped to the embed allowlist. */\n scopes?: string[];\n /** Handle lifetime. Default 60; must be > 0; cap 300 (5 min). */\n handle_lifetime_seconds?: number;\n /** Minted-token lifetime after exchange. Default 3600; must be > 0; cap 86400 (24h). */\n token_lifetime_seconds?: number;\n}\n\n/** `POST /api/embed/iframe-urls` response shape. */\nexport interface EmbedIframeUrlResult {\n /** Drop straight into `<iframe src>`. */\n signed_url: string;\n /** Correlation id for the integrator's audit log. */\n handle_id: string;\n /** RFC3339 HANDLE expiry — not the token expiry. */\n expires_at: string;\n surface: string;\n loan_id: string;\n borrower_id: string;\n scopes: string[];\n}\n\n/** `client.embed.sessions` — mint and revoke embed-session tokens. */\nexport class EmbedSessionsResource {\n constructor(private readonly transport: Transport) {}\n\n private request<T>(method: HttpMethod, path: string, options?: InternalRequestOptions): Promise<T> {\n return this.transport.request<T>(method, path, options ?? {});\n }\n\n /**\n * Mint an embed-session token for a loan.\n *\n * POST /api/embed/sessions\n *\n * Server skips idempotency (`[SkipIdempotency]`); the SDK still sends the\n * auto-generated `Idempotency-Key` header (harmless — the server ignores it).\n * Returns the `ss_embed_*` token plus session metadata (shown once).\n */\n create<T = EmbedSessionResult>(\n params: CreateEmbedSessionParams,\n options?: RequestOptions,\n ): Promise<T> {\n return this.request<T>('POST', '/api/embed/sessions', { body: params, ...options });\n }\n\n /**\n * Revoke an embed session early — useful when the integrator's UI flow ends\n * before the natural expiry, or when reissuing after a logout. Honors\n * `Idempotency-Key`. Resolves to `void` on the `204 No Content`.\n *\n * DELETE /api/embed/sessions/{sessionId}\n */\n revoke<T = void>(sessionId: string, options?: RequestOptions): Promise<T> {\n return this.request<T>(\n 'DELETE',\n `/api/embed/sessions/${encodeURIComponent(sessionId)}`,\n options,\n );\n }\n}\n\n/** `client.embed.iframeUrls` — mint signed iframe-URL handles. */\nexport class EmbedIframeUrlsResource {\n constructor(private readonly transport: Transport) {}\n\n private request<T>(method: HttpMethod, path: string, options?: InternalRequestOptions): Promise<T> {\n return this.transport.request<T>(method, path, options ?? {});\n }\n\n /**\n * Mint a signed-URL handle to drop into an `<iframe src>`. The browser then\n * exchanges the handle (single-use) for an embed token at the iframe origin.\n *\n * POST /api/embed/iframe-urls\n */\n create<T = EmbedIframeUrlResult>(\n params: CreateEmbedIframeUrlParams,\n options?: RequestOptions,\n ): Promise<T> {\n return this.request<T>('POST', '/api/embed/iframe-urls', { body: params, ...options });\n }\n}\n\n/**\n * Top-level `client.embed` namespace. Surfaces the `sessions` and `iframeUrls`\n * sub-resources.\n */\nexport class EmbedResource {\n /** `embed.sessions` — mint/revoke embed-session tokens. */\n public readonly sessions: EmbedSessionsResource;\n /** `embed.iframeUrls` — mint signed iframe-URL handles. */\n public readonly iframeUrls: EmbedIframeUrlsResource;\n\n constructor(transport: Transport) {\n this.sessions = new EmbedSessionsResource(transport);\n this.iframeUrls = new EmbedIframeUrlsResource(transport);\n }\n}\n","/**\n * The `SpreadSpace` client facade.\n *\n * Thin ergonomic namespaces over the transport + helpers. These wrap the\n * cross-cutting flows a generator can't produce (cursor pagination, the async\n * operation waiter, the three-step upload sequence). A fully typed\n * method-per-endpoint surface is expected to come from a generated core in CI;\n * until then `request()` is the escape hatch to any endpoint not yet wrapped.\n *\n * Constructed from `{ apiKey?, baseUrl?, apiVersion?, timeoutMs?, maxRetries?,\n * fetch? }`. An `ss_test_` key routes to the isolated sandbox tenant; `ss_live_`\n * operates on real workspace data. Both hit the same base URL.\n *\n * Money note: amounts decode to a JS `number` (TS has no decimal type). See the\n * README precision caveat — do not treat them as exact decimals.\n */\n\nimport { DEFAULT_BASE_URL, Transport } from './transport.js';\nimport type { HttpMethod } from './transport.js';\nimport { DEFAULT_API_VERSION } from './version.js';\nimport type { InternalRequestOptions, RequestOptions } from './types.js';\n\nimport { paginate } from './helpers/pagination.js';\nimport type { AsyncPager } from './helpers/pagination.js';\nimport {\n cancelOperation,\n createExtractionExport,\n getOperation,\n} from './helpers/operations.js';\nimport type {\n AsyncOperation,\n AsyncOperationHandle,\n ExtractionExportParams,\n} from './helpers/operations.js';\nimport { JobHandle, uploadDocument } from './helpers/upload.js';\nimport type { UploadOptions, UploadSource } from './helpers/upload.js';\nimport { WebhooksResource } from './resources/webhooks.js';\nimport { EmbedResource } from './resources/embed.js';\n\n/** Resolve a stored fallback API key from the environment, when present. */\nfunction envApiKey(): string | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined;\n const value = process.env.SPREADSPACE_API_KEY;\n return value && value.length > 0 ? value : undefined;\n}\n\n/** Constructor options for {@link SpreadSpace}. All optional; sensible defaults. */\nexport interface SpreadSpaceOptions {\n /**\n * API key. `ss_live_` -> live tenant, `ss_test_` -> sandbox tenant. Falls back\n * to `SPREADSPACE_API_KEY` when omitted.\n */\n apiKey?: string;\n\n /** Base URL. Defaults to `https://api.spreadspace.ai` (trailing slash trimmed). */\n baseUrl?: string;\n\n /** Override the SDK-pinned `SpreadSpace-Version` header (per-call overridable too). */\n apiVersion?: string;\n\n /** Per-attempt request timeout in ms. Default 60000. Retries get a fresh window. */\n timeoutMs?: number;\n\n /** Max retry attempts after the initial request. Default 2. */\n maxRetries?: number;\n\n /** `fetch` implementation. Defaults to `globalThis.fetch`. Inject a mock in tests. */\n fetch?: typeof fetch;\n\n /**\n * Pre-built transport. When supplied, the connection options above are\n * ignored — used by tests to share a stubbed transport with the helpers.\n */\n transport?: Transport;\n}\n\n/** Filters for {@link SpreadSpace.borrowers}.list. */\nexport interface ListBorrowersParams {\n /** Restrict to borrowers in the intake/triage queue when set. */\n intake?: boolean;\n /** Page size hint forwarded to the server. */\n limit?: number;\n /** Override the `SpreadSpace-Version` header for these page requests. */\n apiVersion?: string;\n}\n\n/** Filters for {@link SpreadSpace.loans}.list. */\nexport interface ListLoansParams {\n /** Scope to one borrower (`GET /api/borrowers/{id}/loans`); omit for all loans. */\n borrowerId?: string;\n /** Page size hint forwarded to the server. */\n limit?: number;\n /** Override the `SpreadSpace-Version` header for these page requests. */\n apiVersion?: string;\n}\n\n/** Filters for {@link SpreadSpace.jobs}.list. */\nexport interface ListJobsParams {\n /** Page size hint forwarded to the server. */\n limit?: number;\n /** Override the `SpreadSpace-Version` header for these page requests. */\n apiVersion?: string;\n}\n\n/** Filters for {@link SpreadSpace.asyncOperations}.list. */\nexport interface ListAsyncOperationsParams {\n /** Filter by operation kind (e.g. `extraction_export`). */\n kind?: string;\n /** Filter by status (`queued` / `running` / `succeeded` / `failed` / `cancelled`). */\n status?: string;\n /** Page size hint forwarded to the server. */\n limit?: number;\n /** Override the `SpreadSpace-Version` header for these page requests. */\n apiVersion?: string;\n}\n\n/** `client.borrowers` — list borrowers. */\nclass BorrowersResource {\n constructor(private readonly transport: Transport) {}\n\n /** Paginate `GET /api/borrowers`. Lazy async-iterable of borrower records. */\n list<T = Record<string, unknown>>(params: ListBorrowersParams = {}): AsyncPager<T> {\n return paginate<T>(this.transport, '/api/borrowers', {\n params: { limit: params.limit, intake: params.intake },\n apiVersion: params.apiVersion,\n });\n }\n}\n\n/** `client.loans` — list loans, optionally scoped to a borrower. */\nclass LoansResource {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Paginate loans. With `borrowerId`, hits `GET /api/borrowers/{id}/loans`;\n * otherwise the org-wide `GET /api/loans`.\n */\n list<T = Record<string, unknown>>(params: ListLoansParams = {}): AsyncPager<T> {\n const path = params.borrowerId\n ? `/api/borrowers/${encodeURIComponent(params.borrowerId)}/loans`\n : '/api/loans';\n return paginate<T>(this.transport, path, {\n params: { limit: params.limit },\n apiVersion: params.apiVersion,\n });\n }\n}\n\n/** `client.jobs` — list document-package jobs. */\nclass JobsResource {\n constructor(private readonly transport: Transport) {}\n\n /** Paginate `GET /api/jobs`. */\n list<T = Record<string, unknown>>(params: ListJobsParams = {}): AsyncPager<T> {\n return paginate<T>(this.transport, '/api/jobs', {\n params: { limit: params.limit },\n apiVersion: params.apiVersion,\n });\n }\n}\n\n/** `client.asyncOperations` — list / get / cancel long-running operations. */\nclass AsyncOperationsResource {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Paginate `GET /api/async-operations`. Divergent envelope: items live under\n * `operations` (no `data`/`limit`), so the items key is overridden here.\n */\n list<T = Record<string, unknown>>(params: ListAsyncOperationsParams = {}): AsyncPager<T> {\n return paginate<T>(this.transport, '/api/async-operations', {\n params: { limit: params.limit, kind: params.kind, status: params.status },\n itemsKey: 'operations',\n apiVersion: params.apiVersion,\n });\n }\n\n /** `GET /api/async-operations/{id}` — fetch one operation's current state. */\n get(operationId: string, options?: RequestOptions): Promise<AsyncOperation> {\n return getOperation(this.transport, operationId, options);\n }\n\n /**\n * `POST /api/async-operations/{id}/cancel`. Cancelling a terminal\n * (non-cancelled) op -> 409 (`ConflictError`); cancelling an already-cancelled\n * op is idempotent (200).\n */\n cancel(operationId: string, options?: RequestOptions): Promise<AsyncOperation> {\n return cancelOperation(this.transport, operationId, options);\n }\n}\n\n/** `client.exports` — create extraction exports and poll their operations. */\nclass ExportsResource {\n constructor(private readonly transport: Transport) {}\n\n /**\n * `POST /api/async-operations/extraction_export`. All fields optional; `null`/\n * `undefined` are omitted from the body. Returns a handle whose `.wait()` polls\n * the operation to a terminal state.\n */\n create(\n params: ExtractionExportParams = {},\n options?: RequestOptions,\n ): Promise<AsyncOperationHandle> {\n return createExtractionExport(this.transport, params, options);\n }\n\n /** `GET /api/async-operations/{id}` — fetch the export operation's state. */\n get(operationId: string, options?: RequestOptions): Promise<AsyncOperation> {\n return getOperation(this.transport, operationId, options);\n }\n}\n\n/** `client.documents` — upload a file and poll its job. */\nclass DocumentsResource {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Upload a document: presigned-url -> S3 PUT -> confirm-upload. `file` may be a\n * path string, raw bytes, a `Blob`/`File`, or a `Readable`/iterable of chunks.\n * With `wait: true`, polls to a terminal job status before resolving.\n */\n upload(file: UploadSource, options?: UploadOptions): Promise<JobHandle> {\n return uploadDocument(this.transport, file, options);\n }\n\n /** `GET /api/documents/{jobId}/status` — fetch one job's current status body. */\n status(jobId: string): Promise<Record<string, unknown>> {\n return new JobHandle(this.transport, jobId).status();\n }\n}\n\n/**\n * Entry point. `new SpreadSpace({ apiKey: 'ss_test_...' })` or set\n * `SPREADSPACE_API_KEY`.\n *\n * const client = new SpreadSpace({ apiKey: 'ss_test_...' });\n * for await (const borrower of client.borrowers.list()) {\n * // ...\n * }\n */\nexport class SpreadSpace {\n /** Borrowers: `list()`. */\n public readonly borrowers: BorrowersResource;\n /** Loans: `list({ borrowerId?, limit? })`. */\n public readonly loans: LoansResource;\n /** Jobs: `list()`. */\n public readonly jobs: JobsResource;\n /** Async operations: `list({ kind?, status? })` / `get(id)` / `cancel(id)`. */\n public readonly asyncOperations: AsyncOperationsResource;\n /** Extraction exports: `create(...)` / `get(id)`. */\n public readonly exports: ExportsResource;\n /** Documents: `upload(file, opts)` / `status(jobId)`. */\n public readonly documents: DocumentsResource;\n /** Webhooks: `create/list/retrieve/update/delete` endpoints + static `verifySignature`/`verifyAndParse`. */\n public readonly webhooks: WebhooksResource;\n /** Embed: `sessions.create(...)` mints a loan-scoped `ss_embed_` token. */\n public readonly embed: EmbedResource;\n\n private readonly _transport: Transport;\n\n constructor(options: SpreadSpaceOptions = {}) {\n if (options.transport) {\n this._transport = options.transport;\n } else {\n const apiKey = options.apiKey ?? envApiKey();\n if (!apiKey) {\n throw new TypeError(\n 'SpreadSpace requires an apiKey. Pass { apiKey } or set SPREADSPACE_API_KEY. ' +\n 'Issue a key from the dashboard at /settings/api-keys.',\n );\n }\n this._transport = new Transport({\n apiKey,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n apiVersion: options.apiVersion ?? DEFAULT_API_VERSION,\n timeout: options.timeoutMs,\n maxRetries: options.maxRetries,\n fetch: options.fetch,\n });\n }\n\n this.borrowers = new BorrowersResource(this._transport);\n this.loans = new LoansResource(this._transport);\n this.jobs = new JobsResource(this._transport);\n this.asyncOperations = new AsyncOperationsResource(this._transport);\n this.exports = new ExportsResource(this._transport);\n this.documents = new DocumentsResource(this._transport);\n this.webhooks = new WebhooksResource(this._transport);\n this.embed = new EmbedResource(this._transport);\n }\n\n /** The underlying configured transport. */\n get transport(): Transport {\n return this._transport;\n }\n\n /**\n * Low-level escape hatch to any endpoint the resource helpers don't wrap.\n * Returns the decoded JSON body (or `undefined` for an empty / 204 response).\n */\n request<T = unknown>(\n method: HttpMethod,\n path: string,\n options?: InternalRequestOptions,\n ): Promise<T> {\n return this._transport.request<T>(method, path, options);\n }\n}\n"],"mappings":"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","names":["defaultSleep","import_node_crypto","defaultSleep","import_node_crypto","isPlainObject"]}