@rendobar/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.
- package/README.md +229 -0
- package/dist/index.cjs +641 -0
- package/dist/index.d.cts +2851 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2851 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +640 -0
- package/dist/index.mjs.map +1 -0
- package/dist/webhooks.cjs +25 -0
- package/dist/webhooks.d.cts +13 -0
- package/dist/webhooks.d.cts.map +1 -0
- package/dist/webhooks.d.mts +13 -0
- package/dist/webhooks.d.mts.map +1 -0
- package/dist/webhooks.mjs +26 -0
- package/dist/webhooks.mjs.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["sleep","TERMINAL_STATUSES","ReconnectingWebSocket"],"sources":["../src/errors.ts","../src/lib/request.ts","../src/resources/jobs.ts","../src/resources/billing.ts","../src/resources/uploads.ts","../src/resources/webhooks.ts","../src/resources/api-keys.ts","../src/resources/orgs.ts","../src/resources/batches.ts","../src/resources/assets.ts","../src/resources/team.ts","../src/realtime/client.ts","../src/client.ts"],"sourcesContent":["/**\n * SDK error class. All API errors are thrown as ApiError.\n * Use isApiError() to narrow in catch blocks.\n */\nexport class ApiError extends Error {\n constructor(\n public readonly code: string,\n public readonly statusCode: number,\n message: string,\n public readonly details?: Record<string, unknown>,\n public readonly retryAfter?: number,\n ) {\n super(message);\n this.name = \"ApiError\";\n }\n}\n\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n","/**\n * Core HTTP request layer. Handles auth, retries, timeouts, envelope\n * unwrapping, and error mapping. No external dependencies beyond fetch.\n */\nimport { ApiError } from \"../errors.js\";\nimport type { ClientConfig } from \"../client.js\";\n\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_MAX_RETRIES = 2;\nconst RETRY_BASE_DELAY = 500;\nconst RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);\n\n// ── Request options ───────────────────────────────────────────\n\nexport interface RequestOptions {\n method?: string;\n /** JSON body — serialized automatically. */\n body?: unknown;\n /** Raw body — sent as-is (for file uploads). Skips JSON serialization + Content-Type. */\n bodyRaw?: BodyInit;\n /** Query parameters. Values are stringified; null/undefined values are skipped. */\n query?: Record<string, string | number | boolean | null | undefined>;\n signal?: AbortSignal;\n /** Return raw Response (for downloads). Skips JSON parsing + unwrapping. */\n raw?: boolean;\n}\n\n// ── Internal types ────────────────────────────────────────────\n\ninterface ErrorBody {\n error?: {\n code?: string;\n message?: string;\n details?: Record<string, unknown>;\n };\n}\n\n// ── Request function factory ──────────────────────────────────\n\nexport function createRequestFn(config: ClientConfig) {\n const {\n baseUrl = \"https://api.rendobar.com\",\n apiKey,\n accessToken,\n credentials,\n orgId,\n timeout = DEFAULT_TIMEOUT,\n maxRetries = DEFAULT_MAX_RETRIES,\n debug = false,\n fetch: fetchFn = globalThis.fetch,\n } = config;\n\n return async function request<T>(\n path: string,\n options: RequestOptions = {},\n ): Promise<T> {\n const url = buildUrl(baseUrl, path, options.query);\n const init = buildInit(options, apiKey, accessToken, credentials, orgId);\n const combinedSignal = combineSignals(options.signal, timeout);\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = calcRetryDelay(attempt, lastError);\n await sleep(delay);\n }\n\n const start = Date.now();\n\n try {\n const response = await fetchFn(url, { ...init, signal: combinedSignal });\n const duration = Date.now() - start;\n\n if (debug) {\n console.debug(`[sdk] ${init.method ?? \"GET\"} ${path} → ${response.status} (${duration}ms)`);\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n if (options.raw) return response as T;\n return parseResponse<T>(await response.json());\n }\n\n const apiError = await parseErrorResponse(response);\n lastError = apiError;\n\n if (!RETRYABLE_STATUS_CODES.has(response.status) || attempt === maxRetries) {\n throw apiError;\n }\n } catch (error) {\n if (error instanceof ApiError) {\n if (!RETRYABLE_STATUS_CODES.has(error.statusCode) || attempt === maxRetries) {\n throw error;\n }\n lastError = error;\n continue;\n }\n // Network error or abort — don't retry aborts\n if (isAbortError(error)) throw error;\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt === maxRetries) throw lastError;\n }\n }\n\n throw lastError;\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────\n\nfunction buildUrl(baseUrl: string, path: string, query?: RequestOptions[\"query\"]): string {\n let url = `${baseUrl}${path}`;\n if (query) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v != null) params.set(k, String(v));\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n }\n return url;\n}\n\nfunction buildInit(\n options: RequestOptions,\n apiKey?: string,\n accessToken?: string,\n credentials?: RequestCredentials,\n orgId?: string,\n): RequestInit {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (apiKey) {\n headers[\"Authorization\"] = `Bearer ${apiKey}`;\n } else if (accessToken) {\n headers[\"Authorization\"] = `Bearer ${accessToken}`;\n }\n if (orgId) {\n headers[\"X-Org-Id\"] = orgId;\n }\n\n const init: RequestInit = {\n method: options.method ?? \"GET\",\n headers,\n };\n\n if (credentials) {\n init.credentials = credentials;\n }\n\n if (options.bodyRaw !== undefined) {\n init.body = options.bodyRaw;\n delete headers[\"Content-Type\"]; // let runtime set it from body type\n } else if (options.body !== undefined) {\n init.body = JSON.stringify(options.body);\n }\n\n return init;\n}\n\nfunction combineSignals(userSignal: AbortSignal | undefined, timeout: number): AbortSignal {\n const timeoutSignal = AbortSignal.timeout(timeout);\n if (!userSignal) return timeoutSignal;\n return AbortSignal.any([userSignal, timeoutSignal]);\n}\n\nfunction isDataEnvelope(json: unknown): json is { data: unknown; meta?: unknown } {\n return json !== null && typeof json === \"object\" && \"data\" in json;\n}\n\nfunction parseResponse<T>(json: unknown): T {\n // Unwrap { data: T } envelope for single-item responses.\n // List responses { data: T[], meta } stay as-is (they have 'meta').\n if (isDataEnvelope(json)) {\n if (json.meta !== undefined) return json as T; // Paginated — keep structure\n return json.data as T; // Single-item — unwrap\n }\n return json as T; // No envelope — pass through\n}\n\nasync function parseErrorResponse(response: Response): Promise<ApiError> {\n let code = \"UNKNOWN_ERROR\";\n let message = `Request failed with status ${response.status}`;\n let details: Record<string, unknown> | undefined;\n let retryAfter: number | undefined;\n\n try {\n const body: ErrorBody = await response.json();\n if (body.error) {\n code = body.error.code ?? code;\n message = body.error.message ?? message;\n details = body.error.details;\n }\n } catch {\n // Failed to parse error body — use defaults\n }\n\n if (response.status === 429) {\n const header = response.headers.get(\"Retry-After\");\n if (header) {\n retryAfter = Number(header);\n }\n }\n\n return new ApiError(code, response.status, message, details, retryAfter);\n}\n\nfunction calcRetryDelay(attempt: number, lastError?: Error): number {\n // Respect Retry-After header for 429s\n if (lastError instanceof ApiError && lastError.retryAfter) {\n return lastError.retryAfter * 1000;\n }\n // Exponential backoff: 500ms, 1000ms\n return RETRY_BASE_DELAY * 2 ** (attempt - 1);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { Job, JobCreatedResponse, JobType, PaginationMeta } from \"../types.js\";\nimport type { LogEntryData } from \"@rendobar/shared/events/org-events\";\n\n// ── Types ─────────────────────────────────────────────────────\n\nexport type CreateJobParams = {\n type: string;\n inputs?: Record<string, unknown>;\n params?: Record<string, unknown>;\n idempotencyKey?: string;\n forceAsync?: boolean;\n};\n\nexport type ListJobsParams = {\n status?: string;\n type?: string;\n limit?: number;\n offset?: number;\n};\n\nexport type WaitOptions = {\n /** Max wait time in ms. Default: 300_000 (5 minutes). */\n timeout?: number;\n /** Poll interval in ms. Default: 2_000. */\n interval?: number;\n /** Called on each poll with the latest job state. */\n onProgress?: (job: Job) => void;\n /** Abort signal for cancellation. */\n signal?: AbortSignal;\n};\n\nexport type JobListPage = {\n data: Job[];\n meta: PaginationMeta;\n};\n\nconst TERMINAL_STATUSES = new Set([\"complete\", \"failed\", \"cancelled\"]);\n\n// ── Resource ──────────────────────────────────────────────────\n\nexport function createJobsResource(request: RequestFn) {\n return {\n async create(params: CreateJobParams, options?: { signal?: AbortSignal }): Promise<JobCreatedResponse> {\n return request<JobCreatedResponse>(\"/jobs\", {\n method: \"POST\",\n body: params,\n signal: options?.signal,\n });\n },\n\n async get(id: string, options?: { signal?: AbortSignal }): Promise<Job> {\n return request<Job>(`/jobs/${id}`, { signal: options?.signal });\n },\n\n async list(params?: ListJobsParams, options?: { signal?: AbortSignal }): Promise<JobListPage> {\n return request<JobListPage>(\"/jobs\", {\n query: params,\n signal: options?.signal,\n });\n },\n\n async *listAll(params?: Omit<ListJobsParams, \"offset\">): AsyncGenerator<Job> {\n let offset = 0;\n const limit = params?.limit ?? 50;\n\n while (true) {\n const page = await request<JobListPage>(\"/jobs\", {\n query: { ...params, limit, offset },\n });\n\n for (const job of page.data) {\n yield job;\n }\n\n if (offset + page.data.length >= page.meta.total) break;\n offset += limit;\n }\n },\n\n async wait(id: string, options: WaitOptions = {}): Promise<Job> {\n const {\n timeout = 300_000,\n interval = 2_000,\n onProgress,\n signal,\n } = options;\n\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n if (signal?.aborted) {\n throw new DOMException(\"Wait aborted\", \"AbortError\");\n }\n\n const job = await request<Job>(`/jobs/${id}`, { signal });\n onProgress?.(job);\n\n if (TERMINAL_STATUSES.has(job.status)) return job;\n\n const remaining = deadline - Date.now();\n const delay = Math.min(interval, remaining);\n if (delay <= 0) break;\n\n await sleep(delay, signal);\n }\n\n const job = await request<Job>(`/jobs/${id}`, { signal });\n if (TERMINAL_STATUSES.has(job.status)) return job;\n\n throw new Error(`Job ${id} did not complete within ${timeout}ms (status: ${job.status})`);\n },\n\n async cancel(id: string, options?: { signal?: AbortSignal }): Promise<Job> {\n return request<Job>(`/jobs/${id}/cancel`, { method: \"POST\", signal: options?.signal });\n },\n\n async download(id: string, options?: { signal?: AbortSignal }): Promise<Response> {\n return request<Response>(`/jobs/${id}/download`, { raw: true, signal: options?.signal });\n },\n\n async logs(id: string, options?: { signal?: AbortSignal }): Promise<LogEntryData[]> {\n return request<LogEntryData[]>(`/jobs/${id}/logs`, { signal: options?.signal });\n },\n\n async types(options?: { signal?: AbortSignal }): Promise<JobType[]> {\n return request<JobType[]>(\"/jobs/types\", { signal: options?.signal });\n },\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Wait aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\"abort\", () => {\n clearTimeout(timer);\n reject(new DOMException(\"Wait aborted\", \"AbortError\"));\n }, { once: true });\n });\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { BillingState, Transaction, UsageSummary, PaginationMeta, BillingInvoice, PaymentMethod } from \"../types.js\";\n\nexport type UsageParams = {\n start?: string;\n end?: string;\n};\n\nexport type TransactionListParams = {\n page?: number;\n limit?: number;\n};\n\nexport function createBillingResource(request: RequestFn) {\n return {\n async state(options?: { signal?: AbortSignal }): Promise<BillingState> {\n return request<BillingState>(\"/billing/state\", { signal: options?.signal });\n },\n\n async usage(params?: UsageParams, options?: { signal?: AbortSignal }): Promise<UsageSummary> {\n return request<UsageSummary>(\"/billing/usage\", { query: params, signal: options?.signal });\n },\n\n async transactions(params?: TransactionListParams, options?: { signal?: AbortSignal }): Promise<{ data: Transaction[]; meta: PaginationMeta }> {\n return request(\"/billing/transactions\", { query: params, signal: options?.signal });\n },\n\n async checkoutCredits(amount: number, options?: { signal?: AbortSignal }): Promise<{ checkoutUrl: string; checkoutId: string }> {\n return request(\"/billing/checkout/credits\", { method: \"POST\", body: { amount }, signal: options?.signal });\n },\n\n async checkoutPro(options?: { signal?: AbortSignal }): Promise<{ checkoutUrl: string; checkoutId: string }> {\n return request(\"/billing/checkout/pro\", { method: \"POST\", signal: options?.signal });\n },\n\n async cancelSubscription(options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/billing/subscription\", { method: \"PATCH\", body: { cancelAtPeriodEnd: true }, signal: options?.signal });\n },\n\n async reactivateSubscription(options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/billing/subscription\", { method: \"PATCH\", body: { cancelAtPeriodEnd: false }, signal: options?.signal });\n },\n\n async history(params?: { page?: number; limit?: number }, options?: { signal?: AbortSignal }): Promise<{ data: BillingInvoice[]; meta: PaginationMeta }> {\n return request(\"/billing/history\", { query: params, signal: options?.signal });\n },\n\n async paymentMethods(options?: { signal?: AbortSignal }): Promise<PaymentMethod[]> {\n return request(\"/billing/payment-methods\", { signal: options?.signal });\n },\n\n async deletePaymentMethod(id: string, options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(`/billing/payment-methods/${id}`, { method: \"DELETE\", signal: options?.signal });\n },\n\n async portalUrl(options?: { signal?: AbortSignal }): Promise<{ url: string }> {\n return request(\"/billing/payment-methods/portal-url\", { signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\n\nexport type UploadResult = {\n downloadUrl: string;\n};\n\nexport function createUploadsResource(request: RequestFn) {\n return {\n /**\n * Upload a file to ephemeral storage. Returns a download URL to use as job input.\n * Files auto-delete after 24 hours.\n *\n * @param file - File content (Blob, ReadableStream, ArrayBuffer, etc.)\n * @param options.filename - Optional filename hint for Content-Disposition\n */\n async upload(\n file: BodyInit,\n options?: { filename?: string; signal?: AbortSignal },\n ): Promise<UploadResult> {\n return request<UploadResult>(\"/uploads\", {\n method: \"POST\",\n bodyRaw: file,\n query: options?.filename ? { filename: options.filename } : undefined,\n signal: options?.signal,\n });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { WebhookEndpoint, WebhookDelivery, PaginationMeta } from \"../types.js\";\n\nexport type CreateWebhookParams = {\n name: string;\n url: string;\n subscribedEvents: string[];\n};\n\nexport type UpdateWebhookParams = {\n name?: string;\n url?: string;\n subscribedEvents?: string[];\n active?: boolean;\n};\n\nexport type ListDeliveriesParams = {\n endpointId?: string;\n event?: string;\n status?: string;\n jobId?: string;\n limit?: number;\n offset?: number;\n};\n\nexport function createWebhooksResource(request: RequestFn) {\n return {\n async create(params: CreateWebhookParams, options?: { signal?: AbortSignal }): Promise<WebhookEndpoint> {\n return request(\"/webhooks/endpoints\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async list(options?: { signal?: AbortSignal }): Promise<{ data: WebhookEndpoint[]; meta: { total: number } }> {\n return request(\"/webhooks/endpoints\", { signal: options?.signal });\n },\n\n async get(id: string, options?: { signal?: AbortSignal }): Promise<WebhookEndpoint> {\n return request(`/webhooks/endpoints/${id}`, { signal: options?.signal });\n },\n\n async update(id: string, params: UpdateWebhookParams, options?: { signal?: AbortSignal }): Promise<WebhookEndpoint> {\n return request(`/webhooks/endpoints/${id}`, { method: \"PATCH\", body: params, signal: options?.signal });\n },\n\n async delete(id: string, options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(`/webhooks/endpoints/${id}`, { method: \"DELETE\", signal: options?.signal });\n },\n\n async rotateSecret(id: string, options?: { signal?: AbortSignal }): Promise<WebhookEndpoint> {\n return request(`/webhooks/endpoints/${id}/secret`, { method: \"POST\", signal: options?.signal });\n },\n\n async test(id: string, options?: { signal?: AbortSignal }): Promise<{ delivered: boolean; statusCode: number | null; responseTimeMs: number }> {\n return request(`/webhooks/endpoints/${id}/test`, { method: \"POST\", signal: options?.signal });\n },\n\n async listDeliveries(params?: ListDeliveriesParams, options?: { signal?: AbortSignal }): Promise<{ data: WebhookDelivery[]; meta: PaginationMeta }> {\n return request(\"/webhooks/deliveries\", { query: params, signal: options?.signal });\n },\n\n async retryDelivery(id: string, options?: { signal?: AbortSignal }): Promise<WebhookDelivery> {\n return request(`/webhooks/deliveries/${id}/retry`, { method: \"POST\", signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { ApiKey } from \"../types.js\";\n\nexport function createApiKeysResource(request: RequestFn) {\n return {\n async create(params: { name?: string; expiresIn?: number }, options?: { signal?: AbortSignal }): Promise<ApiKey & { key: string }> {\n return request(\"/api-keys\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async list(options?: { signal?: AbortSignal }): Promise<ApiKey[]> {\n return request(\"/api-keys\", { signal: options?.signal });\n },\n\n async revoke(id: string, options?: { signal?: AbortSignal }): Promise<{ id: string; revoked: boolean }> {\n return request(`/api-keys/${id}`, { method: \"DELETE\", signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { Organization } from \"../types.js\";\n\nexport type OrgCurrentResponse = {\n org: Organization & { memberRole: string };\n subscription: {\n planId: string;\n status: string;\n provider: string;\n currentPeriodStart: number;\n currentPeriodEnd: number;\n suspendedAt: number | null;\n };\n plan: {\n id: string;\n name: string;\n limits: Record<string, number>;\n features: Record<string, boolean>;\n };\n balance: {\n balance: number;\n rollover: number;\n balanceFormatted: string;\n };\n};\n\nexport function createOrgsResource(request: RequestFn) {\n return {\n async list(options?: { signal?: AbortSignal }): Promise<Organization[]> {\n return request(\"/orgs\", { signal: options?.signal });\n },\n\n async current(options?: { signal?: AbortSignal }): Promise<OrgCurrentResponse> {\n return request(\"/orgs/current\", { signal: options?.signal });\n },\n\n async create(params: { name: string; slug?: string }, options?: { signal?: AbortSignal }): Promise<Organization> {\n return request(\"/orgs\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async update(params: { name?: string; slug?: string }, options?: { signal?: AbortSignal }): Promise<Organization> {\n return request(\"/orgs/current\", { method: \"PATCH\", body: params, signal: options?.signal });\n },\n\n async delete(options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/orgs/current\", { method: \"DELETE\", signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\nimport type { CreateJobParams } from \"./jobs.js\";\n\nexport type BatchResult = {\n batchId: string;\n jobCount: number;\n jobIds: string[];\n status: string;\n};\n\nexport function createBatchesResource(request: RequestFn) {\n return {\n async create(params: { jobs: CreateJobParams[] }, options?: { signal?: AbortSignal }): Promise<BatchResult> {\n return request(\"/batches\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async get(id: string, options?: { signal?: AbortSignal }): Promise<{ id: string; orgId: string; jobCount: number; completedCount: number; createdAt: number }> {\n return request(`/batches/${id}`, { signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\n\nexport type Asset = {\n id: string;\n orgId: string;\n type: string;\n name: string;\n filename: string;\n r2Key: string;\n fileSize: number;\n metadata: string | null;\n scope: string;\n status: string;\n category: string | null;\n thumbnailKey: string | null;\n createdAt: number;\n updatedAt: number;\n};\n\nexport function createAssetsResource(request: RequestFn) {\n return {\n async templates(params?: { category?: string; limit?: number }, options?: { signal?: AbortSignal }): Promise<Asset[]> {\n return request(\"/assets/templates\", { query: params ? { ...params } : undefined, signal: options?.signal });\n },\n };\n}\n","import type { RequestFn } from \"../client.js\";\n\nexport function createTeamResource(request: RequestFn) {\n return {\n async invite(params: { email: string; role: string }, options?: { signal?: AbortSignal }): Promise<{ id: string; email: string; role: string; expiresAt: number }> {\n return request(\"/team/invite\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async changeRole(params: { memberId: string; role: string }, options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/team/role\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async remove(params: { memberId: string }, options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/team/remove\", { method: \"POST\", body: params, signal: options?.signal });\n },\n\n async revokeInvitation(params: { invitationId: string }, options?: { signal?: AbortSignal }): Promise<void> {\n await request<undefined>(\"/team/revoke-invitation\", { method: \"POST\", body: params, signal: options?.signal });\n },\n };\n}\n","/**\n * Realtime event client using partysocket for reconnection\n * and the Rendobar OrgHub protocol for event replay + dedup.\n *\n * Protocol:\n * 1. On connect: send { type: \"init\", lastEventId }\n * 2. Server replays buffered events (each has monotonic `id` field)\n * 3. Server sends { type: \"replay.done\" } — switch to live mode\n * 4. Server sends { type: \"resync\" } — buffer overflow, caller should full-refresh\n * 5. Dedup: skip events where id <= lastEventId\n */\nimport { WebSocket as ReconnectingWebSocket } from \"partysocket\";\nimport type { OrgEvent } from \"./types.js\";\n\n// ── Types ─────────────────────────────────────────────────────\n\nexport interface RealtimeConnectOptions {\n onEvent: (event: OrgEvent) => void;\n onLive?: () => void;\n onReconnect?: () => void;\n onResync?: () => void;\n lastEventId?: number;\n}\n\nexport interface RealtimeConnection {\n disconnect: () => void;\n}\n\nexport interface JobSubscribeOptions {\n onProgress?: (event: Extract<OrgEvent, { type: \"job.progress\" }>) => void;\n onStep?: (event: Extract<OrgEvent, { type: \"job.step\" }>) => void;\n onLog?: (event: Extract<OrgEvent, { type: \"job.log\" }>) => void;\n onStatus?: (event: Extract<OrgEvent, { type: \"job.status\" }>) => void;\n onResult?: (event: Extract<OrgEvent, { type: \"job.result\" }>) => void;\n onComplete?: (event: Extract<OrgEvent, { type: \"job.status\" }>) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface JobSubscription {\n unsubscribe: () => void;\n}\n\n// ── JSON parsing ──────────────────────────────────────────────\n\ninterface ParsedMessage {\n type: string;\n id?: number;\n [key: string]: unknown;\n}\n\nfunction parseMessage(raw: string): ParsedMessage | null {\n try {\n const data = JSON.parse(raw);\n if (data && typeof data === \"object\" && typeof data.type === \"string\") {\n return data;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n// ── Connection factory ────────────────────────────────────────\n\nconst WS_OPTIONS = {\n maxReconnectionDelay: 30_000,\n minReconnectionDelay: 1_000,\n reconnectionDelayGrowFactor: 1.5,\n maxRetries: Infinity,\n} as const;\n\nconst TERMINAL_STATUSES = new Set([\"complete\", \"failed\", \"cancelled\"]);\n\n/** Known OrgEvent types — only dispatch events with recognized types. */\nconst KNOWN_EVENT_TYPES = new Set([\n \"job.created\", \"job.status\", \"job.result\", \"job.progress\", \"job.step\", \"job.log\",\n \"balance.updated\", \"subscription.updated\", \"notification\", \"org.updated\",\n]);\n\n// ── RealtimeClient ────────────────────────────────────────────\n\n/**\n * Create a realtime client for WebSocket event streaming.\n *\n * Auth: session cookie (credentials: \"include\") OR API key via ?token= query param.\n * The client appends the token automatically when apiKey is provided.\n */\nexport function createRealtimeClient(baseUrl: string, options?: { apiKey?: string; accessToken?: string }) {\n const wsUrl = baseUrl.replace(\"https://\", \"wss://\").replace(\"http://\", \"ws://\");\n const token = options?.apiKey ?? options?.accessToken;\n const tokenSuffix = token ? `?token=${encodeURIComponent(token)}` : \"\";\n\n return {\n connect(options: RealtimeConnectOptions): RealtimeConnection {\n let lastEventId = options.lastEventId ?? 0;\n\n const ws = new ReconnectingWebSocket(`${wsUrl}/events/ws${tokenSuffix}`, undefined, WS_OPTIONS);\n\n ws.addEventListener(\"open\", () => {\n options.onReconnect?.();\n ws.send(JSON.stringify({ type: \"init\", lastEventId }));\n });\n\n ws.addEventListener(\"message\", (evt: MessageEvent) => {\n if (typeof evt.data !== \"string\") return;\n const data = parseMessage(evt.data);\n if (!data) return;\n\n if (typeof data.id === \"number\") {\n if (data.id <= lastEventId) return;\n lastEventId = data.id;\n }\n\n if (data.type === \"replay.done\") {\n options.onLive?.();\n return;\n }\n if (data.type === \"resync\") {\n options.onResync?.();\n return;\n }\n\n if (KNOWN_EVENT_TYPES.has(data.type)) {\n options.onEvent(data as OrgEvent);\n }\n });\n\n return {\n disconnect: () => ws.close(),\n };\n },\n\n subscribeJob(jobId: string, options: JobSubscribeOptions): JobSubscription {\n const separator = tokenSuffix ? \"&\" : \"?\";\n const ws = new ReconnectingWebSocket(`${wsUrl}/events/ws${tokenSuffix}${separator}jobId=${jobId}`, undefined, WS_OPTIONS);\n\n ws.addEventListener(\"open\", () => {\n ws.send(JSON.stringify({ type: \"init\", lastEventId: 0 }));\n });\n\n ws.addEventListener(\"message\", (evt: MessageEvent) => {\n if (typeof evt.data !== \"string\") return;\n const data = parseMessage(evt.data);\n if (!data || data.type === \"replay.done\" || data.type === \"resync\") return;\n\n try {\n dispatchJobEvent(data, options);\n } catch (err) {\n options.onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n });\n\n return {\n unsubscribe: () => ws.close(),\n };\n },\n };\n}\n\nfunction dispatchJobEvent(event: ParsedMessage, options: JobSubscribeOptions): void {\n // OrgEvent is a discriminated union on `type`. We route based on the type field.\n // The event object is structurally compatible — no cast needed for the dispatch,\n // but the callback types use Extract<OrgEvent, ...> which requires the full union.\n // Since we validated `type` is a string in parseMessage, the routing is safe.\n switch (event.type) {\n case \"job.progress\":\n options.onProgress?.(event as Extract<OrgEvent, { type: \"job.progress\" }>);\n break;\n case \"job.step\":\n options.onStep?.(event as Extract<OrgEvent, { type: \"job.step\" }>);\n break;\n case \"job.log\":\n options.onLog?.(event as Extract<OrgEvent, { type: \"job.log\" }>);\n break;\n case \"job.status\": {\n const statusEvent = event as Extract<OrgEvent, { type: \"job.status\" }>;\n options.onStatus?.(statusEvent);\n if (TERMINAL_STATUSES.has(statusEvent.status)) {\n options.onComplete?.(statusEvent);\n }\n break;\n }\n case \"job.result\":\n options.onResult?.(event as Extract<OrgEvent, { type: \"job.result\" }>);\n break;\n }\n}\n","/**\n * SDK client factory. Creates a configured client with resource namespaces.\n */\nimport { createRequestFn } from \"./lib/request.js\";\nimport { createJobsResource } from \"./resources/jobs.js\";\nimport { createBillingResource } from \"./resources/billing.js\";\nimport { createUploadsResource } from \"./resources/uploads.js\";\nimport { createWebhooksResource } from \"./resources/webhooks.js\";\nimport { createApiKeysResource } from \"./resources/api-keys.js\";\nimport { createOrgsResource } from \"./resources/orgs.js\";\nimport { createBatchesResource } from \"./resources/batches.js\";\nimport { createAssetsResource } from \"./resources/assets.js\";\nimport { createTeamResource } from \"./resources/team.js\";\nimport { createRealtimeClient } from \"./realtime/client.js\";\n\n// ── Config ────────────────────────────────────────────────────\n\nexport interface ClientConfig {\n /** API key (Bearer rb_...). Mutually exclusive with credentials. */\n apiKey?: string;\n /** OAuth access token. Mutually exclusive with apiKey. */\n accessToken?: string;\n /** Base URL for the API. Default: \"https://api.rendobar.com\" */\n baseUrl?: string;\n /** Request timeout in ms. Default: 30000 */\n timeout?: number;\n /** Max retry attempts for 429/5xx. Default: 2 */\n maxRetries?: number;\n /** Default org ID (sent as X-Org-Id header). */\n orgId?: string;\n /** Log request metadata to console.debug. Default: false */\n debug?: boolean;\n /** Custom fetch function for testing or special runtimes. */\n fetch?: typeof globalThis.fetch;\n /** Standard fetch credentials option. Use \"include\" for cookie auth (dashboard). */\n credentials?: RequestCredentials;\n}\n\n// ── Request function type (shared between client and resources) ─\n\nexport type RequestFn = <T>(path: string, options?: import(\"./lib/request.js\").RequestOptions) => Promise<T>;\n\n// ── Factory ───────────────────────────────────────────────────\n\nexport function createClient(config: ClientConfig = {}) {\n const baseUrl = config.baseUrl ?? \"https://api.rendobar.com\";\n const request = createRequestFn(config);\n\n return {\n jobs: createJobsResource(request),\n billing: createBillingResource(request),\n uploads: createUploadsResource(request),\n webhooks: createWebhooksResource(request),\n apiKeys: createApiKeysResource(request),\n orgs: createOrgsResource(request),\n batches: createBatchesResource(request),\n assets: createAssetsResource(request),\n team: createTeamResource(request),\n realtime: createRealtimeClient(baseUrl, { apiKey: config.apiKey, accessToken: config.accessToken }),\n };\n}\n\nexport type RendobarClient = ReturnType<typeof createClient>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/lib/webhook-verify.ts
|
|
3
|
+
/**
|
|
4
|
+
* Verify a Rendobar webhook signature using HMAC-SHA256.
|
|
5
|
+
* Works in any runtime with Web Crypto API (Node 18+, Deno, CF Workers, browser).
|
|
6
|
+
*
|
|
7
|
+
* Usage in a webhook handler:
|
|
8
|
+
* import { verifyWebhookSignature } from "@rendobar/sdk/webhooks";
|
|
9
|
+
* const valid = await verifyWebhookSignature(body, signature, secret);
|
|
10
|
+
*/
|
|
11
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
12
|
+
const expected = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
13
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
|
|
14
|
+
name: "HMAC",
|
|
15
|
+
hash: "SHA-256"
|
|
16
|
+
}, false, ["sign"]);
|
|
17
|
+
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
|
|
18
|
+
const computed = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
19
|
+
if (expected.length !== computed.length) return false;
|
|
20
|
+
let mismatch = 0;
|
|
21
|
+
for (let i = 0; i < expected.length; i++) mismatch |= expected.charCodeAt(i) ^ computed.charCodeAt(i);
|
|
22
|
+
return mismatch === 0;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/lib/webhook-verify.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Verify a Rendobar webhook signature using HMAC-SHA256.
|
|
4
|
+
* Works in any runtime with Web Crypto API (Node 18+, Deno, CF Workers, browser).
|
|
5
|
+
*
|
|
6
|
+
* Usage in a webhook handler:
|
|
7
|
+
* import { verifyWebhookSignature } from "@rendobar/sdk/webhooks";
|
|
8
|
+
* const valid = await verifyWebhookSignature(body, signature, secret);
|
|
9
|
+
*/
|
|
10
|
+
declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { verifyWebhookSignature };
|
|
13
|
+
//# sourceMappingURL=webhooks.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.cts","names":[],"sources":["../src/lib/webhook-verify.ts"],"mappings":";;AAQA;;;;;;;iBAAsB,sBAAA,CACpB,OAAA,UACA,SAAA,UACA,MAAA,WACC,OAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/lib/webhook-verify.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Verify a Rendobar webhook signature using HMAC-SHA256.
|
|
4
|
+
* Works in any runtime with Web Crypto API (Node 18+, Deno, CF Workers, browser).
|
|
5
|
+
*
|
|
6
|
+
* Usage in a webhook handler:
|
|
7
|
+
* import { verifyWebhookSignature } from "@rendobar/sdk/webhooks";
|
|
8
|
+
* const valid = await verifyWebhookSignature(body, signature, secret);
|
|
9
|
+
*/
|
|
10
|
+
declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { verifyWebhookSignature };
|
|
13
|
+
//# sourceMappingURL=webhooks.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.mts","names":[],"sources":["../src/lib/webhook-verify.ts"],"mappings":";;AAQA;;;;;;;iBAAsB,sBAAA,CACpB,OAAA,UACA,SAAA,UACA,MAAA,WACC,OAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/lib/webhook-verify.ts
|
|
2
|
+
/**
|
|
3
|
+
* Verify a Rendobar webhook signature using HMAC-SHA256.
|
|
4
|
+
* Works in any runtime with Web Crypto API (Node 18+, Deno, CF Workers, browser).
|
|
5
|
+
*
|
|
6
|
+
* Usage in a webhook handler:
|
|
7
|
+
* import { verifyWebhookSignature } from "@rendobar/sdk/webhooks";
|
|
8
|
+
* const valid = await verifyWebhookSignature(body, signature, secret);
|
|
9
|
+
*/
|
|
10
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
11
|
+
const expected = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
12
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
|
|
13
|
+
name: "HMAC",
|
|
14
|
+
hash: "SHA-256"
|
|
15
|
+
}, false, ["sign"]);
|
|
16
|
+
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
|
|
17
|
+
const computed = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
18
|
+
if (expected.length !== computed.length) return false;
|
|
19
|
+
let mismatch = 0;
|
|
20
|
+
for (let i = 0; i < expected.length; i++) mismatch |= expected.charCodeAt(i) ^ computed.charCodeAt(i);
|
|
21
|
+
return mismatch === 0;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { verifyWebhookSignature };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=webhooks.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.mjs","names":[],"sources":["../src/lib/webhook-verify.ts"],"sourcesContent":["/**\n * Verify a Rendobar webhook signature using HMAC-SHA256.\n * Works in any runtime with Web Crypto API (Node 18+, Deno, CF Workers, browser).\n *\n * Usage in a webhook handler:\n * import { verifyWebhookSignature } from \"@rendobar/sdk/webhooks\";\n * const valid = await verifyWebhookSignature(body, signature, secret);\n */\nexport async function verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n): Promise<boolean> {\n // Signature format: \"sha256=<hex>\"\n const expected = signature.startsWith(\"sha256=\") ? signature.slice(7) : signature;\n\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n\n const sig = await crypto.subtle.sign(\"HMAC\", key, new TextEncoder().encode(payload));\n const computed = Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // Timing-safe comparison via subtle.timingSafeEqual where available,\n // fallback to constant-time string comparison\n if (expected.length !== computed.length) return false;\n\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ computed.charCodeAt(i);\n }\n return mismatch === 0;\n}\n"],"mappings":";;;;;;;;;AAQA,eAAsB,uBACpB,SACA,WACA,QACkB;CAElB,MAAM,WAAW,UAAU,WAAW,UAAU,GAAG,UAAU,MAAM,EAAE,GAAG;CAExE,MAAM,MAAM,MAAM,OAAO,OAAO,UAC9B,OACA,IAAI,aAAa,CAAC,OAAO,OAAO,EAChC;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,OAAO,CACT;CAED,MAAM,MAAM,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,aAAa,CAAC,OAAO,QAAQ,CAAC;CACpF,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC,CAC7C,KAAK,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAC3C,KAAK,GAAG;AAIX,KAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;CAEhD,IAAI,WAAW;AACf,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,aAAY,SAAS,WAAW,EAAE,GAAG,SAAS,WAAW,EAAE;AAE7D,QAAO,aAAa"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rendobar/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "TypeScript client for the Rendobar media processing API",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.cts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.cts",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"import": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./webhooks": {
|
|
16
|
+
"types": "./dist/webhooks.d.cts",
|
|
17
|
+
"require": "./dist/webhooks.cjs",
|
|
18
|
+
"import": "./dist/webhooks.mjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"partysocket": "^1.1.16"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rendobar/shared": "workspace:*",
|
|
44
|
+
"tsdown": "^0.21.7",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^4.0.18"
|
|
47
|
+
}
|
|
48
|
+
}
|