@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
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { WebSocket } from "partysocket";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
/**
|
|
4
|
+
* SDK error class. All API errors are thrown as ApiError.
|
|
5
|
+
* Use isApiError() to narrow in catch blocks.
|
|
6
|
+
*/
|
|
7
|
+
var ApiError = class extends Error {
|
|
8
|
+
constructor(code, statusCode, message, details, retryAfter) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.details = details;
|
|
13
|
+
this.retryAfter = retryAfter;
|
|
14
|
+
this.name = "ApiError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function isApiError(error) {
|
|
18
|
+
return error instanceof ApiError;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/lib/request.ts
|
|
22
|
+
/**
|
|
23
|
+
* Core HTTP request layer. Handles auth, retries, timeouts, envelope
|
|
24
|
+
* unwrapping, and error mapping. No external dependencies beyond fetch.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
27
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
28
|
+
const RETRY_BASE_DELAY = 500;
|
|
29
|
+
const RETRYABLE_STATUS_CODES = new Set([
|
|
30
|
+
429,
|
|
31
|
+
500,
|
|
32
|
+
502,
|
|
33
|
+
503,
|
|
34
|
+
504
|
|
35
|
+
]);
|
|
36
|
+
function createRequestFn(config) {
|
|
37
|
+
const { baseUrl = "https://api.rendobar.com", apiKey, accessToken, credentials, orgId, timeout = DEFAULT_TIMEOUT, maxRetries = DEFAULT_MAX_RETRIES, debug = false, fetch: fetchFn = globalThis.fetch } = config;
|
|
38
|
+
return async function request(path, options = {}) {
|
|
39
|
+
const url = buildUrl(baseUrl, path, options.query);
|
|
40
|
+
const init = buildInit(options, apiKey, accessToken, credentials, orgId);
|
|
41
|
+
const combinedSignal = combineSignals(options.signal, timeout);
|
|
42
|
+
let lastError;
|
|
43
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
44
|
+
if (attempt > 0) await sleep$1(calcRetryDelay(attempt, lastError));
|
|
45
|
+
const start = Date.now();
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetchFn(url, {
|
|
48
|
+
...init,
|
|
49
|
+
signal: combinedSignal
|
|
50
|
+
});
|
|
51
|
+
const duration = Date.now() - start;
|
|
52
|
+
if (debug) console.debug(`[sdk] ${init.method ?? "GET"} ${path} → ${response.status} (${duration}ms)`);
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
if (response.status === 204) return void 0;
|
|
55
|
+
if (options.raw) return response;
|
|
56
|
+
return parseResponse(await response.json());
|
|
57
|
+
}
|
|
58
|
+
const apiError = await parseErrorResponse(response);
|
|
59
|
+
lastError = apiError;
|
|
60
|
+
if (!RETRYABLE_STATUS_CODES.has(response.status) || attempt === maxRetries) throw apiError;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof ApiError) {
|
|
63
|
+
if (!RETRYABLE_STATUS_CODES.has(error.statusCode) || attempt === maxRetries) throw error;
|
|
64
|
+
lastError = error;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (isAbortError(error)) throw error;
|
|
68
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
69
|
+
if (attempt === maxRetries) throw lastError;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw lastError;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function buildUrl(baseUrl, path, query) {
|
|
76
|
+
let url = `${baseUrl}${path}`;
|
|
77
|
+
if (query) {
|
|
78
|
+
const params = new URLSearchParams();
|
|
79
|
+
for (const [k, v] of Object.entries(query)) if (v != null) params.set(k, String(v));
|
|
80
|
+
const qs = params.toString();
|
|
81
|
+
if (qs) url += `?${qs}`;
|
|
82
|
+
}
|
|
83
|
+
return url;
|
|
84
|
+
}
|
|
85
|
+
function buildInit(options, apiKey, accessToken, credentials, orgId) {
|
|
86
|
+
const headers = { "Content-Type": "application/json" };
|
|
87
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
88
|
+
else if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
|
|
89
|
+
if (orgId) headers["X-Org-Id"] = orgId;
|
|
90
|
+
const init = {
|
|
91
|
+
method: options.method ?? "GET",
|
|
92
|
+
headers
|
|
93
|
+
};
|
|
94
|
+
if (credentials) init.credentials = credentials;
|
|
95
|
+
if (options.bodyRaw !== void 0) {
|
|
96
|
+
init.body = options.bodyRaw;
|
|
97
|
+
delete headers["Content-Type"];
|
|
98
|
+
} else if (options.body !== void 0) init.body = JSON.stringify(options.body);
|
|
99
|
+
return init;
|
|
100
|
+
}
|
|
101
|
+
function combineSignals(userSignal, timeout) {
|
|
102
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
103
|
+
if (!userSignal) return timeoutSignal;
|
|
104
|
+
return AbortSignal.any([userSignal, timeoutSignal]);
|
|
105
|
+
}
|
|
106
|
+
function isDataEnvelope(json) {
|
|
107
|
+
return json !== null && typeof json === "object" && "data" in json;
|
|
108
|
+
}
|
|
109
|
+
function parseResponse(json) {
|
|
110
|
+
if (isDataEnvelope(json)) {
|
|
111
|
+
if (json.meta !== void 0) return json;
|
|
112
|
+
return json.data;
|
|
113
|
+
}
|
|
114
|
+
return json;
|
|
115
|
+
}
|
|
116
|
+
async function parseErrorResponse(response) {
|
|
117
|
+
let code = "UNKNOWN_ERROR";
|
|
118
|
+
let message = `Request failed with status ${response.status}`;
|
|
119
|
+
let details;
|
|
120
|
+
let retryAfter;
|
|
121
|
+
try {
|
|
122
|
+
const body = await response.json();
|
|
123
|
+
if (body.error) {
|
|
124
|
+
code = body.error.code ?? code;
|
|
125
|
+
message = body.error.message ?? message;
|
|
126
|
+
details = body.error.details;
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
if (response.status === 429) {
|
|
130
|
+
const header = response.headers.get("Retry-After");
|
|
131
|
+
if (header) retryAfter = Number(header);
|
|
132
|
+
}
|
|
133
|
+
return new ApiError(code, response.status, message, details, retryAfter);
|
|
134
|
+
}
|
|
135
|
+
function calcRetryDelay(attempt, lastError) {
|
|
136
|
+
if (lastError instanceof ApiError && lastError.retryAfter) return lastError.retryAfter * 1e3;
|
|
137
|
+
return RETRY_BASE_DELAY * 2 ** (attempt - 1);
|
|
138
|
+
}
|
|
139
|
+
function sleep$1(ms) {
|
|
140
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
141
|
+
}
|
|
142
|
+
function isAbortError(error) {
|
|
143
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/resources/jobs.ts
|
|
147
|
+
const TERMINAL_STATUSES$1 = new Set([
|
|
148
|
+
"complete",
|
|
149
|
+
"failed",
|
|
150
|
+
"cancelled"
|
|
151
|
+
]);
|
|
152
|
+
function createJobsResource(request) {
|
|
153
|
+
return {
|
|
154
|
+
async create(params, options) {
|
|
155
|
+
return request("/jobs", {
|
|
156
|
+
method: "POST",
|
|
157
|
+
body: params,
|
|
158
|
+
signal: options?.signal
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
async get(id, options) {
|
|
162
|
+
return request(`/jobs/${id}`, { signal: options?.signal });
|
|
163
|
+
},
|
|
164
|
+
async list(params, options) {
|
|
165
|
+
return request("/jobs", {
|
|
166
|
+
query: params,
|
|
167
|
+
signal: options?.signal
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
async *listAll(params) {
|
|
171
|
+
let offset = 0;
|
|
172
|
+
const limit = params?.limit ?? 50;
|
|
173
|
+
while (true) {
|
|
174
|
+
const page = await request("/jobs", { query: {
|
|
175
|
+
...params,
|
|
176
|
+
limit,
|
|
177
|
+
offset
|
|
178
|
+
} });
|
|
179
|
+
for (const job of page.data) yield job;
|
|
180
|
+
if (offset + page.data.length >= page.meta.total) break;
|
|
181
|
+
offset += limit;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
async wait(id, options = {}) {
|
|
185
|
+
const { timeout = 3e5, interval = 2e3, onProgress, signal } = options;
|
|
186
|
+
const deadline = Date.now() + timeout;
|
|
187
|
+
while (Date.now() < deadline) {
|
|
188
|
+
if (signal?.aborted) throw new DOMException("Wait aborted", "AbortError");
|
|
189
|
+
const job = await request(`/jobs/${id}`, { signal });
|
|
190
|
+
onProgress?.(job);
|
|
191
|
+
if (TERMINAL_STATUSES$1.has(job.status)) return job;
|
|
192
|
+
const remaining = deadline - Date.now();
|
|
193
|
+
const delay = Math.min(interval, remaining);
|
|
194
|
+
if (delay <= 0) break;
|
|
195
|
+
await sleep(delay, signal);
|
|
196
|
+
}
|
|
197
|
+
const job = await request(`/jobs/${id}`, { signal });
|
|
198
|
+
if (TERMINAL_STATUSES$1.has(job.status)) return job;
|
|
199
|
+
throw new Error(`Job ${id} did not complete within ${timeout}ms (status: ${job.status})`);
|
|
200
|
+
},
|
|
201
|
+
async cancel(id, options) {
|
|
202
|
+
return request(`/jobs/${id}/cancel`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
signal: options?.signal
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
async download(id, options) {
|
|
208
|
+
return request(`/jobs/${id}/download`, {
|
|
209
|
+
raw: true,
|
|
210
|
+
signal: options?.signal
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
async logs(id, options) {
|
|
214
|
+
return request(`/jobs/${id}/logs`, { signal: options?.signal });
|
|
215
|
+
},
|
|
216
|
+
async types(options) {
|
|
217
|
+
return request("/jobs/types", { signal: options?.signal });
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function sleep(ms, signal) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
if (signal?.aborted) {
|
|
224
|
+
reject(new DOMException("Wait aborted", "AbortError"));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const timer = setTimeout(resolve, ms);
|
|
228
|
+
signal?.addEventListener("abort", () => {
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
reject(new DOMException("Wait aborted", "AbortError"));
|
|
231
|
+
}, { once: true });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/resources/billing.ts
|
|
236
|
+
function createBillingResource(request) {
|
|
237
|
+
return {
|
|
238
|
+
async state(options) {
|
|
239
|
+
return request("/billing/state", { signal: options?.signal });
|
|
240
|
+
},
|
|
241
|
+
async usage(params, options) {
|
|
242
|
+
return request("/billing/usage", {
|
|
243
|
+
query: params,
|
|
244
|
+
signal: options?.signal
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
async transactions(params, options) {
|
|
248
|
+
return request("/billing/transactions", {
|
|
249
|
+
query: params,
|
|
250
|
+
signal: options?.signal
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
async checkoutCredits(amount, options) {
|
|
254
|
+
return request("/billing/checkout/credits", {
|
|
255
|
+
method: "POST",
|
|
256
|
+
body: { amount },
|
|
257
|
+
signal: options?.signal
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
async checkoutPro(options) {
|
|
261
|
+
return request("/billing/checkout/pro", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
signal: options?.signal
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
async cancelSubscription(options) {
|
|
267
|
+
await request("/billing/subscription", {
|
|
268
|
+
method: "PATCH",
|
|
269
|
+
body: { cancelAtPeriodEnd: true },
|
|
270
|
+
signal: options?.signal
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
async reactivateSubscription(options) {
|
|
274
|
+
await request("/billing/subscription", {
|
|
275
|
+
method: "PATCH",
|
|
276
|
+
body: { cancelAtPeriodEnd: false },
|
|
277
|
+
signal: options?.signal
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
async history(params, options) {
|
|
281
|
+
return request("/billing/history", {
|
|
282
|
+
query: params,
|
|
283
|
+
signal: options?.signal
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
async paymentMethods(options) {
|
|
287
|
+
return request("/billing/payment-methods", { signal: options?.signal });
|
|
288
|
+
},
|
|
289
|
+
async deletePaymentMethod(id, options) {
|
|
290
|
+
await request(`/billing/payment-methods/${id}`, {
|
|
291
|
+
method: "DELETE",
|
|
292
|
+
signal: options?.signal
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
async portalUrl(options) {
|
|
296
|
+
return request("/billing/payment-methods/portal-url", { signal: options?.signal });
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/resources/uploads.ts
|
|
302
|
+
function createUploadsResource(request) {
|
|
303
|
+
return { async upload(file, options) {
|
|
304
|
+
return request("/uploads", {
|
|
305
|
+
method: "POST",
|
|
306
|
+
bodyRaw: file,
|
|
307
|
+
query: options?.filename ? { filename: options.filename } : void 0,
|
|
308
|
+
signal: options?.signal
|
|
309
|
+
});
|
|
310
|
+
} };
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/resources/webhooks.ts
|
|
314
|
+
function createWebhooksResource(request) {
|
|
315
|
+
return {
|
|
316
|
+
async create(params, options) {
|
|
317
|
+
return request("/webhooks/endpoints", {
|
|
318
|
+
method: "POST",
|
|
319
|
+
body: params,
|
|
320
|
+
signal: options?.signal
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
async list(options) {
|
|
324
|
+
return request("/webhooks/endpoints", { signal: options?.signal });
|
|
325
|
+
},
|
|
326
|
+
async get(id, options) {
|
|
327
|
+
return request(`/webhooks/endpoints/${id}`, { signal: options?.signal });
|
|
328
|
+
},
|
|
329
|
+
async update(id, params, options) {
|
|
330
|
+
return request(`/webhooks/endpoints/${id}`, {
|
|
331
|
+
method: "PATCH",
|
|
332
|
+
body: params,
|
|
333
|
+
signal: options?.signal
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
async delete(id, options) {
|
|
337
|
+
await request(`/webhooks/endpoints/${id}`, {
|
|
338
|
+
method: "DELETE",
|
|
339
|
+
signal: options?.signal
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
async rotateSecret(id, options) {
|
|
343
|
+
return request(`/webhooks/endpoints/${id}/secret`, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
signal: options?.signal
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
async test(id, options) {
|
|
349
|
+
return request(`/webhooks/endpoints/${id}/test`, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
signal: options?.signal
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
async listDeliveries(params, options) {
|
|
355
|
+
return request("/webhooks/deliveries", {
|
|
356
|
+
query: params,
|
|
357
|
+
signal: options?.signal
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
async retryDelivery(id, options) {
|
|
361
|
+
return request(`/webhooks/deliveries/${id}/retry`, {
|
|
362
|
+
method: "POST",
|
|
363
|
+
signal: options?.signal
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/resources/api-keys.ts
|
|
370
|
+
function createApiKeysResource(request) {
|
|
371
|
+
return {
|
|
372
|
+
async create(params, options) {
|
|
373
|
+
return request("/api-keys", {
|
|
374
|
+
method: "POST",
|
|
375
|
+
body: params,
|
|
376
|
+
signal: options?.signal
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
async list(options) {
|
|
380
|
+
return request("/api-keys", { signal: options?.signal });
|
|
381
|
+
},
|
|
382
|
+
async revoke(id, options) {
|
|
383
|
+
return request(`/api-keys/${id}`, {
|
|
384
|
+
method: "DELETE",
|
|
385
|
+
signal: options?.signal
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/resources/orgs.ts
|
|
392
|
+
function createOrgsResource(request) {
|
|
393
|
+
return {
|
|
394
|
+
async list(options) {
|
|
395
|
+
return request("/orgs", { signal: options?.signal });
|
|
396
|
+
},
|
|
397
|
+
async current(options) {
|
|
398
|
+
return request("/orgs/current", { signal: options?.signal });
|
|
399
|
+
},
|
|
400
|
+
async create(params, options) {
|
|
401
|
+
return request("/orgs", {
|
|
402
|
+
method: "POST",
|
|
403
|
+
body: params,
|
|
404
|
+
signal: options?.signal
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
async update(params, options) {
|
|
408
|
+
return request("/orgs/current", {
|
|
409
|
+
method: "PATCH",
|
|
410
|
+
body: params,
|
|
411
|
+
signal: options?.signal
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
async delete(options) {
|
|
415
|
+
await request("/orgs/current", {
|
|
416
|
+
method: "DELETE",
|
|
417
|
+
signal: options?.signal
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/resources/batches.ts
|
|
424
|
+
function createBatchesResource(request) {
|
|
425
|
+
return {
|
|
426
|
+
async create(params, options) {
|
|
427
|
+
return request("/batches", {
|
|
428
|
+
method: "POST",
|
|
429
|
+
body: params,
|
|
430
|
+
signal: options?.signal
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
async get(id, options) {
|
|
434
|
+
return request(`/batches/${id}`, { signal: options?.signal });
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/resources/assets.ts
|
|
440
|
+
function createAssetsResource(request) {
|
|
441
|
+
return { async templates(params, options) {
|
|
442
|
+
return request("/assets/templates", {
|
|
443
|
+
query: params ? { ...params } : void 0,
|
|
444
|
+
signal: options?.signal
|
|
445
|
+
});
|
|
446
|
+
} };
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/resources/team.ts
|
|
450
|
+
function createTeamResource(request) {
|
|
451
|
+
return {
|
|
452
|
+
async invite(params, options) {
|
|
453
|
+
return request("/team/invite", {
|
|
454
|
+
method: "POST",
|
|
455
|
+
body: params,
|
|
456
|
+
signal: options?.signal
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
async changeRole(params, options) {
|
|
460
|
+
await request("/team/role", {
|
|
461
|
+
method: "POST",
|
|
462
|
+
body: params,
|
|
463
|
+
signal: options?.signal
|
|
464
|
+
});
|
|
465
|
+
},
|
|
466
|
+
async remove(params, options) {
|
|
467
|
+
await request("/team/remove", {
|
|
468
|
+
method: "POST",
|
|
469
|
+
body: params,
|
|
470
|
+
signal: options?.signal
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
async revokeInvitation(params, options) {
|
|
474
|
+
await request("/team/revoke-invitation", {
|
|
475
|
+
method: "POST",
|
|
476
|
+
body: params,
|
|
477
|
+
signal: options?.signal
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/realtime/client.ts
|
|
484
|
+
/**
|
|
485
|
+
* Realtime event client using partysocket for reconnection
|
|
486
|
+
* and the Rendobar OrgHub protocol for event replay + dedup.
|
|
487
|
+
*
|
|
488
|
+
* Protocol:
|
|
489
|
+
* 1. On connect: send { type: "init", lastEventId }
|
|
490
|
+
* 2. Server replays buffered events (each has monotonic `id` field)
|
|
491
|
+
* 3. Server sends { type: "replay.done" } — switch to live mode
|
|
492
|
+
* 4. Server sends { type: "resync" } — buffer overflow, caller should full-refresh
|
|
493
|
+
* 5. Dedup: skip events where id <= lastEventId
|
|
494
|
+
*/
|
|
495
|
+
function parseMessage(raw) {
|
|
496
|
+
try {
|
|
497
|
+
const data = JSON.parse(raw);
|
|
498
|
+
if (data && typeof data === "object" && typeof data.type === "string") return data;
|
|
499
|
+
return null;
|
|
500
|
+
} catch {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const WS_OPTIONS = {
|
|
505
|
+
maxReconnectionDelay: 3e4,
|
|
506
|
+
minReconnectionDelay: 1e3,
|
|
507
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
508
|
+
maxRetries: Infinity
|
|
509
|
+
};
|
|
510
|
+
const TERMINAL_STATUSES = new Set([
|
|
511
|
+
"complete",
|
|
512
|
+
"failed",
|
|
513
|
+
"cancelled"
|
|
514
|
+
]);
|
|
515
|
+
/** Known OrgEvent types — only dispatch events with recognized types. */
|
|
516
|
+
const KNOWN_EVENT_TYPES = new Set([
|
|
517
|
+
"job.created",
|
|
518
|
+
"job.status",
|
|
519
|
+
"job.result",
|
|
520
|
+
"job.progress",
|
|
521
|
+
"job.step",
|
|
522
|
+
"job.log",
|
|
523
|
+
"balance.updated",
|
|
524
|
+
"subscription.updated",
|
|
525
|
+
"notification",
|
|
526
|
+
"org.updated"
|
|
527
|
+
]);
|
|
528
|
+
/**
|
|
529
|
+
* Create a realtime client for WebSocket event streaming.
|
|
530
|
+
*
|
|
531
|
+
* Auth: session cookie (credentials: "include") OR API key via ?token= query param.
|
|
532
|
+
* The client appends the token automatically when apiKey is provided.
|
|
533
|
+
*/
|
|
534
|
+
function createRealtimeClient(baseUrl, options) {
|
|
535
|
+
const wsUrl = baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
536
|
+
const token = options?.apiKey ?? options?.accessToken;
|
|
537
|
+
const tokenSuffix = token ? `?token=${encodeURIComponent(token)}` : "";
|
|
538
|
+
return {
|
|
539
|
+
connect(options) {
|
|
540
|
+
let lastEventId = options.lastEventId ?? 0;
|
|
541
|
+
const ws = new WebSocket(`${wsUrl}/events/ws${tokenSuffix}`, void 0, WS_OPTIONS);
|
|
542
|
+
ws.addEventListener("open", () => {
|
|
543
|
+
options.onReconnect?.();
|
|
544
|
+
ws.send(JSON.stringify({
|
|
545
|
+
type: "init",
|
|
546
|
+
lastEventId
|
|
547
|
+
}));
|
|
548
|
+
});
|
|
549
|
+
ws.addEventListener("message", (evt) => {
|
|
550
|
+
if (typeof evt.data !== "string") return;
|
|
551
|
+
const data = parseMessage(evt.data);
|
|
552
|
+
if (!data) return;
|
|
553
|
+
if (typeof data.id === "number") {
|
|
554
|
+
if (data.id <= lastEventId) return;
|
|
555
|
+
lastEventId = data.id;
|
|
556
|
+
}
|
|
557
|
+
if (data.type === "replay.done") {
|
|
558
|
+
options.onLive?.();
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (data.type === "resync") {
|
|
562
|
+
options.onResync?.();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (KNOWN_EVENT_TYPES.has(data.type)) options.onEvent(data);
|
|
566
|
+
});
|
|
567
|
+
return { disconnect: () => ws.close() };
|
|
568
|
+
},
|
|
569
|
+
subscribeJob(jobId, options) {
|
|
570
|
+
const ws = new WebSocket(`${wsUrl}/events/ws${tokenSuffix}${tokenSuffix ? "&" : "?"}jobId=${jobId}`, void 0, WS_OPTIONS);
|
|
571
|
+
ws.addEventListener("open", () => {
|
|
572
|
+
ws.send(JSON.stringify({
|
|
573
|
+
type: "init",
|
|
574
|
+
lastEventId: 0
|
|
575
|
+
}));
|
|
576
|
+
});
|
|
577
|
+
ws.addEventListener("message", (evt) => {
|
|
578
|
+
if (typeof evt.data !== "string") return;
|
|
579
|
+
const data = parseMessage(evt.data);
|
|
580
|
+
if (!data || data.type === "replay.done" || data.type === "resync") return;
|
|
581
|
+
try {
|
|
582
|
+
dispatchJobEvent(data, options);
|
|
583
|
+
} catch (err) {
|
|
584
|
+
options.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
return { unsubscribe: () => ws.close() };
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function dispatchJobEvent(event, options) {
|
|
592
|
+
switch (event.type) {
|
|
593
|
+
case "job.progress":
|
|
594
|
+
options.onProgress?.(event);
|
|
595
|
+
break;
|
|
596
|
+
case "job.step":
|
|
597
|
+
options.onStep?.(event);
|
|
598
|
+
break;
|
|
599
|
+
case "job.log":
|
|
600
|
+
options.onLog?.(event);
|
|
601
|
+
break;
|
|
602
|
+
case "job.status": {
|
|
603
|
+
const statusEvent = event;
|
|
604
|
+
options.onStatus?.(statusEvent);
|
|
605
|
+
if (TERMINAL_STATUSES.has(statusEvent.status)) options.onComplete?.(statusEvent);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case "job.result":
|
|
609
|
+
options.onResult?.(event);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region src/client.ts
|
|
615
|
+
/**
|
|
616
|
+
* SDK client factory. Creates a configured client with resource namespaces.
|
|
617
|
+
*/
|
|
618
|
+
function createClient(config = {}) {
|
|
619
|
+
const baseUrl = config.baseUrl ?? "https://api.rendobar.com";
|
|
620
|
+
const request = createRequestFn(config);
|
|
621
|
+
return {
|
|
622
|
+
jobs: createJobsResource(request),
|
|
623
|
+
billing: createBillingResource(request),
|
|
624
|
+
uploads: createUploadsResource(request),
|
|
625
|
+
webhooks: createWebhooksResource(request),
|
|
626
|
+
apiKeys: createApiKeysResource(request),
|
|
627
|
+
orgs: createOrgsResource(request),
|
|
628
|
+
batches: createBatchesResource(request),
|
|
629
|
+
assets: createAssetsResource(request),
|
|
630
|
+
team: createTeamResource(request),
|
|
631
|
+
realtime: createRealtimeClient(baseUrl, {
|
|
632
|
+
apiKey: config.apiKey,
|
|
633
|
+
accessToken: config.accessToken
|
|
634
|
+
})
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
638
|
+
export { ApiError, createClient, isApiError };
|
|
639
|
+
|
|
640
|
+
//# sourceMappingURL=index.mjs.map
|