@royalti/syynk 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 +279 -0
- package/dist/index.d.mts +571 -0
- package/dist/index.d.ts +571 -0
- package/dist/index.js +587 -0
- package/dist/index.mjs +548 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
AuthorizationError: () => AuthorizationError,
|
|
25
|
+
NetworkError: () => NetworkError,
|
|
26
|
+
NotFoundError: () => NotFoundError,
|
|
27
|
+
RateLimitError: () => RateLimitError,
|
|
28
|
+
ServerError: () => ServerError,
|
|
29
|
+
SyynkClient: () => SyynkClient,
|
|
30
|
+
SyynkError: () => SyynkError,
|
|
31
|
+
TimeoutError: () => TimeoutError,
|
|
32
|
+
ValidationError: () => ValidationError,
|
|
33
|
+
isRateLimitError: () => isRateLimitError,
|
|
34
|
+
isRetryableError: () => isRetryableError,
|
|
35
|
+
isSyynkError: () => isSyynkError
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/errors.ts
|
|
40
|
+
var SyynkError = class extends Error {
|
|
41
|
+
/** Error code for programmatic handling */
|
|
42
|
+
code;
|
|
43
|
+
/** HTTP status code */
|
|
44
|
+
status;
|
|
45
|
+
/** Additional error details from the API */
|
|
46
|
+
details;
|
|
47
|
+
constructor(message, code, status, details) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "SyynkError";
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.status = status;
|
|
52
|
+
this.details = details;
|
|
53
|
+
if (Error.captureStackTrace) {
|
|
54
|
+
Error.captureStackTrace(this, this.constructor);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert error to a plain object for serialization
|
|
59
|
+
*/
|
|
60
|
+
toJSON() {
|
|
61
|
+
return {
|
|
62
|
+
name: this.name,
|
|
63
|
+
message: this.message,
|
|
64
|
+
code: this.code,
|
|
65
|
+
status: this.status,
|
|
66
|
+
details: this.details
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var AuthenticationError = class extends SyynkError {
|
|
71
|
+
constructor(message = "Invalid or missing API key", details) {
|
|
72
|
+
super(message, "AUTHENTICATION_ERROR", 401, details);
|
|
73
|
+
this.name = "AuthenticationError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var AuthorizationError = class extends SyynkError {
|
|
77
|
+
constructor(message = "Insufficient permissions for this operation", details) {
|
|
78
|
+
super(message, "AUTHORIZATION_ERROR", 403, details);
|
|
79
|
+
this.name = "AuthorizationError";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var RateLimitError = class extends SyynkError {
|
|
83
|
+
/** Unix timestamp when the rate limit resets */
|
|
84
|
+
resetAt;
|
|
85
|
+
/** Seconds until the rate limit resets */
|
|
86
|
+
retryAfter;
|
|
87
|
+
constructor(message = "Rate limit exceeded", resetAt, details) {
|
|
88
|
+
super(message, "RATE_LIMIT_ERROR", 429, details);
|
|
89
|
+
this.name = "RateLimitError";
|
|
90
|
+
this.resetAt = resetAt;
|
|
91
|
+
this.retryAfter = Math.max(0, Math.ceil(resetAt - Date.now() / 1e3));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the Date object for when the rate limit resets
|
|
95
|
+
*/
|
|
96
|
+
getResetDate() {
|
|
97
|
+
return new Date(this.resetAt * 1e3);
|
|
98
|
+
}
|
|
99
|
+
toJSON() {
|
|
100
|
+
return {
|
|
101
|
+
...super.toJSON(),
|
|
102
|
+
resetAt: this.resetAt,
|
|
103
|
+
retryAfter: this.retryAfter
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var ValidationError = class extends SyynkError {
|
|
108
|
+
/** Field-level validation errors */
|
|
109
|
+
fieldErrors;
|
|
110
|
+
constructor(message = "Invalid request data", details, fieldErrors) {
|
|
111
|
+
super(message, "VALIDATION_ERROR", 400, details);
|
|
112
|
+
this.name = "ValidationError";
|
|
113
|
+
this.fieldErrors = fieldErrors;
|
|
114
|
+
}
|
|
115
|
+
toJSON() {
|
|
116
|
+
return {
|
|
117
|
+
...super.toJSON(),
|
|
118
|
+
fieldErrors: this.fieldErrors
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var NotFoundError = class extends SyynkError {
|
|
123
|
+
/** Type of resource that wasn't found */
|
|
124
|
+
resourceType;
|
|
125
|
+
/** ID of the resource that wasn't found */
|
|
126
|
+
resourceId;
|
|
127
|
+
constructor(message = "Resource not found", resourceType, resourceId, details) {
|
|
128
|
+
super(message, "NOT_FOUND_ERROR", 404, details);
|
|
129
|
+
this.name = "NotFoundError";
|
|
130
|
+
this.resourceType = resourceType;
|
|
131
|
+
this.resourceId = resourceId;
|
|
132
|
+
}
|
|
133
|
+
toJSON() {
|
|
134
|
+
return {
|
|
135
|
+
...super.toJSON(),
|
|
136
|
+
resourceType: this.resourceType,
|
|
137
|
+
resourceId: this.resourceId
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var ServerError = class extends SyynkError {
|
|
142
|
+
/** Request ID for debugging */
|
|
143
|
+
requestId;
|
|
144
|
+
constructor(message = "Internal server error", status = 500, requestId, details) {
|
|
145
|
+
super(message, "SERVER_ERROR", status, details);
|
|
146
|
+
this.name = "ServerError";
|
|
147
|
+
this.requestId = requestId;
|
|
148
|
+
}
|
|
149
|
+
toJSON() {
|
|
150
|
+
return {
|
|
151
|
+
...super.toJSON(),
|
|
152
|
+
requestId: this.requestId
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var NetworkError = class extends SyynkError {
|
|
157
|
+
/** The original error that caused this network error */
|
|
158
|
+
cause;
|
|
159
|
+
constructor(message = "Network request failed", cause) {
|
|
160
|
+
super(message, "NETWORK_ERROR", 0);
|
|
161
|
+
this.name = "NetworkError";
|
|
162
|
+
this.cause = cause;
|
|
163
|
+
}
|
|
164
|
+
toJSON() {
|
|
165
|
+
return {
|
|
166
|
+
...super.toJSON(),
|
|
167
|
+
cause: this.cause?.message
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var TimeoutError = class extends SyynkError {
|
|
172
|
+
/** The timeout duration in milliseconds */
|
|
173
|
+
timeoutMs;
|
|
174
|
+
constructor(message = "Request timed out", timeoutMs) {
|
|
175
|
+
super(message, "TIMEOUT_ERROR", 0);
|
|
176
|
+
this.name = "TimeoutError";
|
|
177
|
+
this.timeoutMs = timeoutMs;
|
|
178
|
+
}
|
|
179
|
+
toJSON() {
|
|
180
|
+
return {
|
|
181
|
+
...super.toJSON(),
|
|
182
|
+
timeoutMs: this.timeoutMs
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function isSyynkError(error) {
|
|
187
|
+
return error instanceof SyynkError;
|
|
188
|
+
}
|
|
189
|
+
function isRateLimitError(error) {
|
|
190
|
+
return error instanceof RateLimitError;
|
|
191
|
+
}
|
|
192
|
+
function isRetryableError(error) {
|
|
193
|
+
if (error instanceof RateLimitError) return true;
|
|
194
|
+
if (error instanceof NetworkError) return true;
|
|
195
|
+
if (error instanceof TimeoutError) return true;
|
|
196
|
+
if (error instanceof ServerError && error.status >= 500) return true;
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/client.ts
|
|
201
|
+
var DEFAULT_BASE_URL = "https://syynk.to";
|
|
202
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
203
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
204
|
+
var INITIAL_RETRY_DELAY = 1e3;
|
|
205
|
+
var SyynkClient = class {
|
|
206
|
+
apiKey;
|
|
207
|
+
baseUrl;
|
|
208
|
+
timeout;
|
|
209
|
+
maxRetries;
|
|
210
|
+
/** Last rate limit info received from the API */
|
|
211
|
+
lastRateLimitInfo = null;
|
|
212
|
+
/**
|
|
213
|
+
* Create a new Syynk API client
|
|
214
|
+
*
|
|
215
|
+
* @param options - Client configuration options
|
|
216
|
+
* @throws {Error} If apiKey is not provided
|
|
217
|
+
*/
|
|
218
|
+
constructor(options) {
|
|
219
|
+
if (!options.apiKey) {
|
|
220
|
+
throw new Error("API key is required");
|
|
221
|
+
}
|
|
222
|
+
this.apiKey = options.apiKey;
|
|
223
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
224
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
225
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get the current rate limit information
|
|
229
|
+
*
|
|
230
|
+
* @returns The last rate limit info received, or null if no requests have been made
|
|
231
|
+
*/
|
|
232
|
+
getRateLimitInfo() {
|
|
233
|
+
return this.lastRateLimitInfo;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Transcribe audio from a URL
|
|
237
|
+
*
|
|
238
|
+
* @param options - Transcription options
|
|
239
|
+
* @returns Transcription result with segments and words
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const result = await client.transcribe({
|
|
244
|
+
* audioUrl: 'https://example.com/song.mp3',
|
|
245
|
+
* language: 'en',
|
|
246
|
+
* });
|
|
247
|
+
* console.log(`Transcribed ${result.segments.length} segments`);
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async transcribe(options) {
|
|
251
|
+
return this.request("/api/v1/transcribe", {
|
|
252
|
+
method: "POST",
|
|
253
|
+
body: JSON.stringify({
|
|
254
|
+
audioUrl: options.audioUrl,
|
|
255
|
+
language: options.language,
|
|
256
|
+
projectName: options.projectName
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Transcribe audio from a file upload
|
|
262
|
+
*
|
|
263
|
+
* Works in both Node.js and browser environments.
|
|
264
|
+
*
|
|
265
|
+
* @param file - Audio file as Blob/File (browser) or ArrayBuffer/Uint8Array (Node.js Buffer works as it extends Uint8Array)
|
|
266
|
+
* @param options - Optional transcription options
|
|
267
|
+
* @returns Transcription result with segments and words
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* // Browser
|
|
272
|
+
* const file = document.querySelector('input[type="file"]').files[0];
|
|
273
|
+
* const result = await client.transcribeFile(file);
|
|
274
|
+
*
|
|
275
|
+
* // Node.js
|
|
276
|
+
* const buffer = fs.readFileSync('song.mp3');
|
|
277
|
+
* const result = await client.transcribeFile(buffer, { filename: 'song.mp3' });
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
async transcribeFile(file, options) {
|
|
281
|
+
const formData = new FormData();
|
|
282
|
+
const filename = options?.filename ?? (file instanceof Blob && "name" in file ? file.name : null) ?? "audio";
|
|
283
|
+
if (file instanceof Blob) {
|
|
284
|
+
formData.append("file", file, filename);
|
|
285
|
+
} else {
|
|
286
|
+
const blob = new Blob([file]);
|
|
287
|
+
formData.append("file", blob, filename);
|
|
288
|
+
}
|
|
289
|
+
if (options?.language) {
|
|
290
|
+
formData.append("language", options.language);
|
|
291
|
+
}
|
|
292
|
+
if (options?.projectName) {
|
|
293
|
+
formData.append("projectName", options.projectName);
|
|
294
|
+
}
|
|
295
|
+
return this.request("/api/v1/transcribe/upload", {
|
|
296
|
+
method: "POST",
|
|
297
|
+
body: formData
|
|
298
|
+
// Don't set Content-Type header - let the browser set it with boundary
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Export segments to a specific format
|
|
303
|
+
*
|
|
304
|
+
* @param options - Export options including format and segments
|
|
305
|
+
* @returns Export result with content and metadata
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const result = await client.export({
|
|
310
|
+
* format: 'lrc',
|
|
311
|
+
* segments: transcription.segments,
|
|
312
|
+
* projectName: 'My Song',
|
|
313
|
+
* });
|
|
314
|
+
*
|
|
315
|
+
* // Save the file
|
|
316
|
+
* fs.writeFileSync(`output.${result.extension}`, result.content);
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
async export(options) {
|
|
320
|
+
return this.request("/api/v1/export", {
|
|
321
|
+
method: "POST",
|
|
322
|
+
body: JSON.stringify({
|
|
323
|
+
format: options.format,
|
|
324
|
+
segments: options.segments,
|
|
325
|
+
words: options.words,
|
|
326
|
+
projectName: options.projectName
|
|
327
|
+
})
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get information about all available export formats
|
|
332
|
+
*
|
|
333
|
+
* @returns Array of format information objects
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* const formats = await client.getFormats();
|
|
338
|
+
* const wordTimingFormats = formats.filter(f => f.supportsWordTiming);
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
async getFormats() {
|
|
342
|
+
return this.request("/api/v1/formats", {
|
|
343
|
+
method: "GET"
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* List projects with optional filtering and pagination
|
|
348
|
+
*
|
|
349
|
+
* @param options - Listing options
|
|
350
|
+
* @returns Paginated list of projects
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* const result = await client.listProjects({
|
|
355
|
+
* status: 'ready',
|
|
356
|
+
* limit: 10,
|
|
357
|
+
* });
|
|
358
|
+
* console.log(`Found ${result.total} projects`);
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
async listProjects(options) {
|
|
362
|
+
const params = new URLSearchParams();
|
|
363
|
+
if (options?.limit !== void 0) {
|
|
364
|
+
params.set("limit", String(options.limit));
|
|
365
|
+
}
|
|
366
|
+
if (options?.offset !== void 0) {
|
|
367
|
+
params.set("offset", String(options.offset));
|
|
368
|
+
}
|
|
369
|
+
if (options?.status) {
|
|
370
|
+
params.set("status", options.status);
|
|
371
|
+
}
|
|
372
|
+
if (options?.type) {
|
|
373
|
+
params.set("type", options.type);
|
|
374
|
+
}
|
|
375
|
+
const queryString = params.toString();
|
|
376
|
+
const path = queryString ? `/api/v1/projects?${queryString}` : "/api/v1/projects";
|
|
377
|
+
return this.request(path, {
|
|
378
|
+
method: "GET"
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get a single project by ID
|
|
383
|
+
*
|
|
384
|
+
* @param id - Project ID
|
|
385
|
+
* @param options - Options for including segments/words
|
|
386
|
+
* @returns Project with segments and words
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```typescript
|
|
390
|
+
* const project = await client.getProject('project-id', {
|
|
391
|
+
* includeWords: true,
|
|
392
|
+
* });
|
|
393
|
+
* console.log(`Project has ${project.segments.length} segments`);
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
async getProject(id, options) {
|
|
397
|
+
const params = new URLSearchParams();
|
|
398
|
+
if (options?.includeWords !== void 0) {
|
|
399
|
+
params.set("includeWords", String(options.includeWords));
|
|
400
|
+
}
|
|
401
|
+
if (options?.includeSegments !== void 0) {
|
|
402
|
+
params.set("includeSegments", String(options.includeSegments));
|
|
403
|
+
}
|
|
404
|
+
const queryString = params.toString();
|
|
405
|
+
const path = queryString ? `/api/v1/projects/${id}?${queryString}` : `/api/v1/projects/${id}`;
|
|
406
|
+
return this.request(path, {
|
|
407
|
+
method: "GET"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Delete a project by ID
|
|
412
|
+
*
|
|
413
|
+
* @param id - Project ID
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```typescript
|
|
417
|
+
* await client.deleteProject('project-id');
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
async deleteProject(id) {
|
|
421
|
+
await this.request(`/api/v1/projects/${id}`, {
|
|
422
|
+
method: "DELETE"
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Make an authenticated API request with automatic retry
|
|
427
|
+
*
|
|
428
|
+
* @param path - API path (starting with /)
|
|
429
|
+
* @param options - Fetch request options
|
|
430
|
+
* @returns Parsed response data
|
|
431
|
+
*/
|
|
432
|
+
async request(path, options, retryCount = 0) {
|
|
433
|
+
const url = `${this.baseUrl}${path}`;
|
|
434
|
+
const headers = {
|
|
435
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
436
|
+
Accept: "application/json"
|
|
437
|
+
};
|
|
438
|
+
if (!(options.body instanceof FormData)) {
|
|
439
|
+
headers["Content-Type"] = "application/json";
|
|
440
|
+
}
|
|
441
|
+
const requestHeaders = {
|
|
442
|
+
...headers,
|
|
443
|
+
...options.headers
|
|
444
|
+
};
|
|
445
|
+
const controller = new AbortController();
|
|
446
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
447
|
+
try {
|
|
448
|
+
const response = await fetch(url, {
|
|
449
|
+
...options,
|
|
450
|
+
headers: requestHeaders,
|
|
451
|
+
signal: controller.signal
|
|
452
|
+
});
|
|
453
|
+
clearTimeout(timeoutId);
|
|
454
|
+
this.parseRateLimitHeaders(response.headers);
|
|
455
|
+
if (response.ok) {
|
|
456
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
const json = await response.json();
|
|
460
|
+
if (json && typeof json === "object" && "data" in json) {
|
|
461
|
+
return json.data;
|
|
462
|
+
}
|
|
463
|
+
return json;
|
|
464
|
+
}
|
|
465
|
+
const error = await this.handleErrorResponse(response);
|
|
466
|
+
if (isRetryableError(error) && retryCount < this.maxRetries) {
|
|
467
|
+
const delay = this.calculateRetryDelay(error, retryCount);
|
|
468
|
+
await this.sleep(delay);
|
|
469
|
+
return this.request(path, options, retryCount + 1);
|
|
470
|
+
}
|
|
471
|
+
throw error;
|
|
472
|
+
} catch (err) {
|
|
473
|
+
clearTimeout(timeoutId);
|
|
474
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
475
|
+
const timeoutError = new TimeoutError(
|
|
476
|
+
`Request timed out after ${this.timeout}ms`,
|
|
477
|
+
this.timeout
|
|
478
|
+
);
|
|
479
|
+
if (retryCount < this.maxRetries) {
|
|
480
|
+
const delay = this.calculateRetryDelay(timeoutError, retryCount);
|
|
481
|
+
await this.sleep(delay);
|
|
482
|
+
return this.request(path, options, retryCount + 1);
|
|
483
|
+
}
|
|
484
|
+
throw timeoutError;
|
|
485
|
+
}
|
|
486
|
+
if (err instanceof TypeError && err.message.includes("fetch")) {
|
|
487
|
+
const networkError = new NetworkError("Network request failed", err);
|
|
488
|
+
if (retryCount < this.maxRetries) {
|
|
489
|
+
const delay = this.calculateRetryDelay(networkError, retryCount);
|
|
490
|
+
await this.sleep(delay);
|
|
491
|
+
return this.request(path, options, retryCount + 1);
|
|
492
|
+
}
|
|
493
|
+
throw networkError;
|
|
494
|
+
}
|
|
495
|
+
if (err instanceof SyynkError) {
|
|
496
|
+
throw err;
|
|
497
|
+
}
|
|
498
|
+
throw new NetworkError(
|
|
499
|
+
err instanceof Error ? err.message : "Unknown error occurred",
|
|
500
|
+
err instanceof Error ? err : void 0
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Parse rate limit headers from response
|
|
506
|
+
*/
|
|
507
|
+
parseRateLimitHeaders(headers) {
|
|
508
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
509
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
510
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
511
|
+
if (limit && remaining && reset) {
|
|
512
|
+
this.lastRateLimitInfo = {
|
|
513
|
+
limit: parseInt(limit, 10),
|
|
514
|
+
remaining: parseInt(remaining, 10),
|
|
515
|
+
reset: parseInt(reset, 10)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Handle error responses and convert to typed errors
|
|
521
|
+
*/
|
|
522
|
+
async handleErrorResponse(response) {
|
|
523
|
+
let errorData = null;
|
|
524
|
+
try {
|
|
525
|
+
errorData = await response.json();
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
const message = errorData?.error?.message ?? response.statusText;
|
|
529
|
+
const code = errorData?.error?.code ?? "UNKNOWN_ERROR";
|
|
530
|
+
const details = errorData?.error?.details;
|
|
531
|
+
switch (response.status) {
|
|
532
|
+
case 400:
|
|
533
|
+
return new ValidationError(message, details);
|
|
534
|
+
case 401:
|
|
535
|
+
return new AuthenticationError(message, details);
|
|
536
|
+
case 403:
|
|
537
|
+
return new AuthorizationError(message, details);
|
|
538
|
+
case 404:
|
|
539
|
+
return new NotFoundError(message, void 0, void 0, details);
|
|
540
|
+
case 429: {
|
|
541
|
+
const resetHeader = response.headers.get("X-RateLimit-Reset");
|
|
542
|
+
const resetAt = resetHeader ? parseInt(resetHeader, 10) : Math.floor(Date.now() / 1e3) + 60;
|
|
543
|
+
return new RateLimitError(message, resetAt, details);
|
|
544
|
+
}
|
|
545
|
+
default:
|
|
546
|
+
if (response.status >= 500) {
|
|
547
|
+
const requestId = response.headers.get("X-Request-Id") ?? void 0;
|
|
548
|
+
return new ServerError(message, response.status, requestId, details);
|
|
549
|
+
}
|
|
550
|
+
return new SyynkError(message, code, response.status, details);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Calculate retry delay with exponential backoff
|
|
555
|
+
*/
|
|
556
|
+
calculateRetryDelay(error, retryCount) {
|
|
557
|
+
if (error instanceof RateLimitError && error.retryAfter > 0) {
|
|
558
|
+
const jitter2 = Math.random() * 1e3;
|
|
559
|
+
return error.retryAfter * 1e3 + jitter2;
|
|
560
|
+
}
|
|
561
|
+
const baseDelay = INITIAL_RETRY_DELAY * Math.pow(2, retryCount);
|
|
562
|
+
const jitter = Math.random() * baseDelay * 0.25;
|
|
563
|
+
return Math.min(baseDelay + jitter, 3e4);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Sleep for the specified duration
|
|
567
|
+
*/
|
|
568
|
+
sleep(ms) {
|
|
569
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
573
|
+
0 && (module.exports = {
|
|
574
|
+
AuthenticationError,
|
|
575
|
+
AuthorizationError,
|
|
576
|
+
NetworkError,
|
|
577
|
+
NotFoundError,
|
|
578
|
+
RateLimitError,
|
|
579
|
+
ServerError,
|
|
580
|
+
SyynkClient,
|
|
581
|
+
SyynkError,
|
|
582
|
+
TimeoutError,
|
|
583
|
+
ValidationError,
|
|
584
|
+
isRateLimitError,
|
|
585
|
+
isRetryableError,
|
|
586
|
+
isSyynkError
|
|
587
|
+
});
|