@sume-com/client 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +541 -2
- package/THIRD-PARTY-NOTICES.txt +714 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.js +11 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +69 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +98 -0
- package/dist/helpers.d.ts +7 -0
- package/dist/helpers.js +49 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.js +12 -0
- package/dist/utils/retry.d.ts +11 -0
- package/dist/utils/retry.js +84 -0
- package/dist/v2/client.d.ts +24 -0
- package/dist/v2/client.js +379 -0
- package/dist/v2/index.d.ts +12 -0
- package/dist/v2/index.js +33 -0
- package/dist/v2/types.d.ts +58 -0
- package/dist/v2/types.js +2 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +79 -4
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchCredentials = fetchCredentials;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
function fetchCredentials() {
|
|
6
|
+
const apiKey = process.env.SUME_API_KEY?.trim();
|
|
7
|
+
if (apiKey) {
|
|
8
|
+
return { apiKey };
|
|
9
|
+
}
|
|
10
|
+
throw new errors_1.CredentialsMissedError();
|
|
11
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ClientConfig {
|
|
2
|
+
timeout?: number;
|
|
3
|
+
maxRetries?: number;
|
|
4
|
+
retryBackoff?: number;
|
|
5
|
+
retryMaxBackoff?: number;
|
|
6
|
+
pollInterval?: number;
|
|
7
|
+
maxPollTime?: number;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
baseURL?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_BASE_URL = "https://api.open.sume.com";
|
|
12
|
+
export declare class Config implements ClientConfig {
|
|
13
|
+
timeout: number;
|
|
14
|
+
maxRetries: number;
|
|
15
|
+
retryBackoff: number;
|
|
16
|
+
retryMaxBackoff: number;
|
|
17
|
+
pollInterval: number;
|
|
18
|
+
maxPollTime: number;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
baseURL: string;
|
|
21
|
+
constructor(config?: Partial<ClientConfig>);
|
|
22
|
+
private validate;
|
|
23
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = exports.DEFAULT_BASE_URL = void 0;
|
|
4
|
+
exports.DEFAULT_BASE_URL = 'https://api.open.sume.com';
|
|
5
|
+
const CONFIG_KEYS = [
|
|
6
|
+
'timeout',
|
|
7
|
+
'maxRetries',
|
|
8
|
+
'retryBackoff',
|
|
9
|
+
'retryMaxBackoff',
|
|
10
|
+
'pollInterval',
|
|
11
|
+
'maxPollTime',
|
|
12
|
+
'headers',
|
|
13
|
+
'baseURL',
|
|
14
|
+
];
|
|
15
|
+
class Config {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.timeout = 120000;
|
|
18
|
+
this.maxRetries = 3;
|
|
19
|
+
this.retryBackoff = 1000;
|
|
20
|
+
this.retryMaxBackoff = 60000;
|
|
21
|
+
this.pollInterval = 2000;
|
|
22
|
+
this.maxPollTime = 300000;
|
|
23
|
+
this.headers = {};
|
|
24
|
+
this.baseURL = exports.DEFAULT_BASE_URL;
|
|
25
|
+
if (config) {
|
|
26
|
+
for (const key of CONFIG_KEYS) {
|
|
27
|
+
if (config[key] !== undefined) {
|
|
28
|
+
Object.assign(this, { [key]: config[key] });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
this.headers = { ...this.headers };
|
|
32
|
+
}
|
|
33
|
+
this.validate();
|
|
34
|
+
}
|
|
35
|
+
validate() {
|
|
36
|
+
if (!(this.timeout > 0)) {
|
|
37
|
+
throw new Error('timeout must be positive');
|
|
38
|
+
}
|
|
39
|
+
if (!(this.maxRetries >= 0)) {
|
|
40
|
+
throw new Error('maxRetries must be non-negative');
|
|
41
|
+
}
|
|
42
|
+
if (this.maxRetries > 5) {
|
|
43
|
+
throw new Error('maxRetries cannot be greater than 5');
|
|
44
|
+
}
|
|
45
|
+
if (!(this.retryBackoff >= 0)) {
|
|
46
|
+
throw new Error('retryBackoff must be non-negative');
|
|
47
|
+
}
|
|
48
|
+
if (!(this.retryMaxBackoff >= 0)) {
|
|
49
|
+
throw new Error('retryMaxBackoff must be non-negative');
|
|
50
|
+
}
|
|
51
|
+
if (!(this.pollInterval > 0)) {
|
|
52
|
+
throw new Error('pollInterval must be positive');
|
|
53
|
+
}
|
|
54
|
+
if (!(this.maxPollTime > 0)) {
|
|
55
|
+
throw new Error('maxPollTime must be positive');
|
|
56
|
+
}
|
|
57
|
+
let url;
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(this.baseURL);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error('baseURL must be an absolute URL');
|
|
63
|
+
}
|
|
64
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
65
|
+
throw new Error('baseURL must be an http(s) URL');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.Config = Config;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare class SumeError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export interface ValidationErrorDetail {
|
|
5
|
+
type: string;
|
|
6
|
+
loc: string[];
|
|
7
|
+
msg: string;
|
|
8
|
+
input?: any;
|
|
9
|
+
ctx?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export declare class APIError extends SumeError {
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
responseData?: any;
|
|
14
|
+
code?: string;
|
|
15
|
+
requestId?: string;
|
|
16
|
+
retryAfter?: number;
|
|
17
|
+
constructor(message: string, statusCode?: number, responseData?: any, retryAfter?: number);
|
|
18
|
+
}
|
|
19
|
+
export declare class AuthenticationError extends APIError {
|
|
20
|
+
statusCode: number;
|
|
21
|
+
constructor(message?: string, responseData?: any);
|
|
22
|
+
}
|
|
23
|
+
export declare class CredentialsMissedError extends SumeError {
|
|
24
|
+
constructor();
|
|
25
|
+
}
|
|
26
|
+
export declare class TimeoutError extends SumeError {
|
|
27
|
+
requestId?: string;
|
|
28
|
+
idempotencyKey?: string;
|
|
29
|
+
constructor(message: string, requestId?: string, idempotencyKey?: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class NotEnoughCreditsError extends APIError {
|
|
32
|
+
statusCode: number;
|
|
33
|
+
constructor(message?: string, responseData?: any);
|
|
34
|
+
}
|
|
35
|
+
export declare class ValidationError extends APIError {
|
|
36
|
+
statusCode: number;
|
|
37
|
+
details?: ValidationErrorDetail[];
|
|
38
|
+
constructor(detail: string | ValidationErrorDetail[] | undefined, responseData?: any);
|
|
39
|
+
}
|
|
40
|
+
export declare class BadInputError extends APIError {
|
|
41
|
+
statusCode: number;
|
|
42
|
+
details?: ValidationErrorDetail[];
|
|
43
|
+
constructor(detail: string | ValidationErrorDetail[] | undefined, responseData?: any);
|
|
44
|
+
}
|
|
45
|
+
export declare class BrowserNotSupportedError extends SumeError {
|
|
46
|
+
constructor();
|
|
47
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserNotSupportedError = exports.BadInputError = exports.ValidationError = exports.NotEnoughCreditsError = exports.TimeoutError = exports.CredentialsMissedError = exports.AuthenticationError = exports.APIError = exports.SumeError = void 0;
|
|
4
|
+
class SumeError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'SumeError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.SumeError = SumeError;
|
|
11
|
+
class APIError extends SumeError {
|
|
12
|
+
constructor(message, statusCode, responseData, retryAfter) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'APIError';
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.responseData = responseData;
|
|
17
|
+
const body = responseData?.error;
|
|
18
|
+
this.code = typeof body?.code === 'string' ? body.code : undefined;
|
|
19
|
+
this.requestId = typeof body?.request_id === 'string' ? body.request_id : undefined;
|
|
20
|
+
this.retryAfter =
|
|
21
|
+
retryAfter ??
|
|
22
|
+
(typeof body?.retry_after_seconds === 'number' ? body.retry_after_seconds : undefined);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.APIError = APIError;
|
|
26
|
+
class AuthenticationError extends APIError {
|
|
27
|
+
constructor(message = 'Authentication failed', responseData) {
|
|
28
|
+
super(message, 401, responseData);
|
|
29
|
+
this.statusCode = 401;
|
|
30
|
+
this.name = 'AuthenticationError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.AuthenticationError = AuthenticationError;
|
|
34
|
+
class CredentialsMissedError extends SumeError {
|
|
35
|
+
constructor() {
|
|
36
|
+
super('API key not found. Set the SUME_API_KEY environment variable, or pass credentials (or apiKey) in config. Create a key at https://open.sume.com/api-keys.');
|
|
37
|
+
this.name = 'CredentialsMissedError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.CredentialsMissedError = CredentialsMissedError;
|
|
41
|
+
class TimeoutError extends SumeError {
|
|
42
|
+
constructor(message, requestId, idempotencyKey) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'TimeoutError';
|
|
45
|
+
this.requestId = requestId;
|
|
46
|
+
this.idempotencyKey = idempotencyKey;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.TimeoutError = TimeoutError;
|
|
50
|
+
class NotEnoughCreditsError extends APIError {
|
|
51
|
+
constructor(message = 'Not enough credits', responseData) {
|
|
52
|
+
super(message, 402, responseData);
|
|
53
|
+
this.statusCode = 402;
|
|
54
|
+
this.name = 'NotEnoughCreditsError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.NotEnoughCreditsError = NotEnoughCreditsError;
|
|
58
|
+
function detailMessage(detail) {
|
|
59
|
+
if (Array.isArray(detail)) {
|
|
60
|
+
return {
|
|
61
|
+
message: detail
|
|
62
|
+
.map((err) => (err.loc.length > 0 ? `${err.loc.join('.')}: ${err.msg}` : err.msg))
|
|
63
|
+
.join(', '),
|
|
64
|
+
details: detail,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (typeof detail === 'string') {
|
|
68
|
+
return { message: detail };
|
|
69
|
+
}
|
|
70
|
+
return { message: 'Check your input params' };
|
|
71
|
+
}
|
|
72
|
+
class ValidationError extends APIError {
|
|
73
|
+
constructor(detail, responseData) {
|
|
74
|
+
const { message, details } = detailMessage(detail);
|
|
75
|
+
super(message, 422, responseData);
|
|
76
|
+
this.statusCode = 422;
|
|
77
|
+
this.name = 'ValidationError';
|
|
78
|
+
this.details = details;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.ValidationError = ValidationError;
|
|
82
|
+
class BadInputError extends APIError {
|
|
83
|
+
constructor(detail, responseData) {
|
|
84
|
+
const { message, details } = detailMessage(detail);
|
|
85
|
+
super(message, 400, responseData);
|
|
86
|
+
this.statusCode = 400;
|
|
87
|
+
this.name = 'BadInputError';
|
|
88
|
+
this.details = details;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.BadInputError = BadInputError;
|
|
92
|
+
class BrowserNotSupportedError extends SumeError {
|
|
93
|
+
constructor() {
|
|
94
|
+
super('This SDK is not supported in browser environments. Please use it in a Node.js environment.');
|
|
95
|
+
this.name = 'BrowserNotSupportedError';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.BrowserNotSupportedError = BrowserNotSupportedError;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { WebhookPayload } from './types';
|
|
2
|
+
export declare const WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
3
|
+
export declare const WEBHOOK_SIGNATURE_VERSION = "sume-v1";
|
|
4
|
+
export declare const WEBHOOK_TIMESTAMP_HEADER = "x-sume-webhook-timestamp";
|
|
5
|
+
export declare const WEBHOOK_SIGNATURE_HEADER = "x-sume-webhook-signature";
|
|
6
|
+
export declare function webhook(url: string): WebhookPayload;
|
|
7
|
+
export declare function verifyWebhook(rawBody: string | Buffer, timestamp: string | string[] | undefined, signature: string | string[] | undefined, secret: string, toleranceSeconds?: number): boolean;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WEBHOOK_SIGNATURE_HEADER = exports.WEBHOOK_TIMESTAMP_HEADER = exports.WEBHOOK_SIGNATURE_VERSION = exports.WEBHOOK_TOLERANCE_SECONDS = void 0;
|
|
4
|
+
exports.webhook = webhook;
|
|
5
|
+
exports.verifyWebhook = verifyWebhook;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
exports.WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
9
|
+
exports.WEBHOOK_SIGNATURE_VERSION = 'sume-v1';
|
|
10
|
+
exports.WEBHOOK_TIMESTAMP_HEADER = 'x-sume-webhook-timestamp';
|
|
11
|
+
exports.WEBHOOK_SIGNATURE_HEADER = 'x-sume-webhook-signature';
|
|
12
|
+
function webhook(url) {
|
|
13
|
+
if (!url || typeof url !== 'string' || url.trim() === '') {
|
|
14
|
+
throw new errors_1.BadInputError('Webhook URL must be a non-empty string');
|
|
15
|
+
}
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = new URL(url);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new errors_1.BadInputError('Webhook URL must be an absolute URL');
|
|
22
|
+
}
|
|
23
|
+
if (parsed.protocol !== 'https:') {
|
|
24
|
+
throw new errors_1.BadInputError('Webhook URL must use HTTPS');
|
|
25
|
+
}
|
|
26
|
+
return { url };
|
|
27
|
+
}
|
|
28
|
+
function verifyWebhook(rawBody, timestamp, signature, secret, toleranceSeconds = exports.WEBHOOK_TOLERANCE_SECONDS) {
|
|
29
|
+
if (!secret || typeof secret !== 'string') {
|
|
30
|
+
throw new errors_1.BadInputError('Webhook signing secret must be a non-empty string');
|
|
31
|
+
}
|
|
32
|
+
const stamp = Array.isArray(timestamp) ? timestamp[0] : timestamp;
|
|
33
|
+
const header = Array.isArray(signature) ? signature.join(',') : signature;
|
|
34
|
+
if (!stamp || !header || !/^\d+$/.test(stamp.trim())) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (Math.abs(Date.now() / 1000 - Number(stamp.trim())) > toleranceSeconds) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const body = typeof rawBody === 'string' ? Buffer.from(rawBody, 'utf8') : rawBody;
|
|
41
|
+
const expected = Buffer.from(`${exports.WEBHOOK_SIGNATURE_VERSION}=` +
|
|
42
|
+
(0, crypto_1.createHmac)('sha256', secret)
|
|
43
|
+
.update(Buffer.concat([Buffer.from(`${stamp.trim()}.`, 'utf8'), body]))
|
|
44
|
+
.digest('hex'), 'utf8');
|
|
45
|
+
return header.split(',').some((entry) => {
|
|
46
|
+
const actual = Buffer.from(entry.trim(), 'utf8');
|
|
47
|
+
return actual.length === expected.length && (0, crypto_1.timingSafeEqual)(actual, expected);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './v2';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./v2"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare enum JobStatus {
|
|
2
|
+
QUEUED = "queued",
|
|
3
|
+
IN_PROGRESS = "in_progress",
|
|
4
|
+
COMPLETED = "completed",
|
|
5
|
+
FAILED = "failed",
|
|
6
|
+
NSFW = "nsfw",
|
|
7
|
+
CANCELED = "canceled"
|
|
8
|
+
}
|
|
9
|
+
export interface Artifact {
|
|
10
|
+
id: string;
|
|
11
|
+
type: 'image' | 'video' | 'audio' | (string & {});
|
|
12
|
+
url: string;
|
|
13
|
+
content_type?: string | null;
|
|
14
|
+
public_artifact_id?: string | null;
|
|
15
|
+
visibility?: string;
|
|
16
|
+
size_bytes?: number | null;
|
|
17
|
+
width?: number | null;
|
|
18
|
+
height?: number | null;
|
|
19
|
+
duration_ms?: number | null;
|
|
20
|
+
checksum_sha256?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface JobError {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
category?: string;
|
|
26
|
+
stage?: string;
|
|
27
|
+
retryable?: boolean;
|
|
28
|
+
retry_after_seconds?: number | null;
|
|
29
|
+
public_reason?: string;
|
|
30
|
+
next_action?: string;
|
|
31
|
+
details?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface WebhookPayload {
|
|
34
|
+
url: string;
|
|
35
|
+
}
|
|
36
|
+
export interface UploadResponse {
|
|
37
|
+
public_url: string;
|
|
38
|
+
upload_url: string;
|
|
39
|
+
upload_method: string;
|
|
40
|
+
upload_headers: Record<string, string>;
|
|
41
|
+
content_type: string;
|
|
42
|
+
expires_at: string;
|
|
43
|
+
asset_id: string;
|
|
44
|
+
}
|
|
45
|
+
export type WebhookEventType = 'job.completed' | 'job.failed' | 'job.canceled';
|
|
46
|
+
export interface WebhookEvent {
|
|
47
|
+
event: WebhookEventType;
|
|
48
|
+
request_id: string;
|
|
49
|
+
job_id: string;
|
|
50
|
+
status: 'OK' | 'ERROR';
|
|
51
|
+
payload?: {
|
|
52
|
+
artifacts: Artifact[];
|
|
53
|
+
};
|
|
54
|
+
error?: JobError;
|
|
55
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JobStatus = void 0;
|
|
4
|
+
var JobStatus;
|
|
5
|
+
(function (JobStatus) {
|
|
6
|
+
JobStatus["QUEUED"] = "queued";
|
|
7
|
+
JobStatus["IN_PROGRESS"] = "in_progress";
|
|
8
|
+
JobStatus["COMPLETED"] = "completed";
|
|
9
|
+
JobStatus["FAILED"] = "failed";
|
|
10
|
+
JobStatus["NSFW"] = "nsfw";
|
|
11
|
+
JobStatus["CANCELED"] = "canceled";
|
|
12
|
+
})(JobStatus || (exports.JobStatus = JobStatus = {}));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RetryConfig {
|
|
2
|
+
maxRetries: number;
|
|
3
|
+
backoff: number;
|
|
4
|
+
maxBackoff: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const sleep: (ms: number) => Promise<void>;
|
|
7
|
+
export declare function parseRetryAfter(value: unknown): number | undefined;
|
|
8
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
9
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
10
|
+
export declare function backoffDelay(attempt: number, config: RetryConfig, error?: unknown): number;
|
|
11
|
+
export declare function retryWithBackoff<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sleep = void 0;
|
|
7
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
8
|
+
exports.isRetryableError = isRetryableError;
|
|
9
|
+
exports.retryAfterMs = retryAfterMs;
|
|
10
|
+
exports.backoffDelay = backoffDelay;
|
|
11
|
+
exports.retryWithBackoff = retryWithBackoff;
|
|
12
|
+
const axios_1 = __importDefault(require("axios"));
|
|
13
|
+
const errors_1 = require("../errors");
|
|
14
|
+
const RETRYABLE_NETWORK_CODES = new Set([
|
|
15
|
+
'ECONNRESET',
|
|
16
|
+
'ETIMEDOUT',
|
|
17
|
+
'ENOTFOUND',
|
|
18
|
+
'ECONNABORTED',
|
|
19
|
+
'ECONNREFUSED',
|
|
20
|
+
'EAI_AGAIN',
|
|
21
|
+
'EPIPE',
|
|
22
|
+
'ERR_NETWORK',
|
|
23
|
+
]);
|
|
24
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
exports.sleep = sleep;
|
|
26
|
+
function parseRetryAfter(value) {
|
|
27
|
+
if (typeof value === 'number') {
|
|
28
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const seconds = Number(value);
|
|
34
|
+
if (Number.isFinite(seconds)) {
|
|
35
|
+
return seconds >= 0 ? seconds : undefined;
|
|
36
|
+
}
|
|
37
|
+
const date = Date.parse(value);
|
|
38
|
+
return Number.isFinite(date) ? Math.max(0, (date - Date.now()) / 1000) : undefined;
|
|
39
|
+
}
|
|
40
|
+
function isRetryableError(error) {
|
|
41
|
+
if (error instanceof errors_1.APIError) {
|
|
42
|
+
if (error.statusCode === undefined) {
|
|
43
|
+
return RETRYABLE_NETWORK_CODES.has(error.code ?? '');
|
|
44
|
+
}
|
|
45
|
+
return error.statusCode === 429 || error.statusCode >= 500;
|
|
46
|
+
}
|
|
47
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
48
|
+
if (error.response) {
|
|
49
|
+
return error.response.status === 429 || error.response.status >= 500;
|
|
50
|
+
}
|
|
51
|
+
return RETRYABLE_NETWORK_CODES.has(error.code ?? '');
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function retryAfterMs(error) {
|
|
56
|
+
if (error instanceof errors_1.APIError) {
|
|
57
|
+
return error.retryAfter === undefined ? undefined : error.retryAfter * 1000;
|
|
58
|
+
}
|
|
59
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
60
|
+
const seconds = parseRetryAfter(error.response?.headers?.['retry-after']);
|
|
61
|
+
return seconds === undefined ? undefined : seconds * 1000;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function backoffDelay(attempt, config, error) {
|
|
66
|
+
const hinted = retryAfterMs(error);
|
|
67
|
+
if (hinted !== undefined) {
|
|
68
|
+
return Math.min(hinted, config.maxBackoff);
|
|
69
|
+
}
|
|
70
|
+
return Math.min(config.backoff * Math.pow(2, attempt) + Math.random() * 1000, config.maxBackoff);
|
|
71
|
+
}
|
|
72
|
+
async function retryWithBackoff(fn, config) {
|
|
73
|
+
for (let attempt = 0;; attempt++) {
|
|
74
|
+
try {
|
|
75
|
+
return await fn();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (attempt >= config.maxRetries || !isRetryableError(error)) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
await (0, exports.sleep)(backoffDelay(attempt, config, error));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ClientConfig } from '../config';
|
|
2
|
+
import { EndpointInputMap, V2Response } from './types';
|
|
3
|
+
export interface V2ClientConfig extends ClientConfig {
|
|
4
|
+
credentials?: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SubscribeOptions<TInput = Record<string, unknown>> {
|
|
8
|
+
input: TInput;
|
|
9
|
+
webhook?: {
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
withPolling?: boolean;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export type SubscribeInput<TEndpoint extends string> = TEndpoint extends keyof EndpointInputMap ? EndpointInputMap[TEndpoint] : Record<string, unknown>;
|
|
16
|
+
export interface SumeClient {
|
|
17
|
+
subscribe<TEndpoint extends string>(endpoint: TEndpoint, options: SubscribeOptions<SubscribeInput<TEndpoint>>): Promise<V2Response>;
|
|
18
|
+
upload(data: Buffer | Uint8Array, contentType: string): Promise<string>;
|
|
19
|
+
uploadImage(imageBuffer: Buffer | Uint8Array, format?: 'jpeg' | 'png' | 'webp'): Promise<string>;
|
|
20
|
+
configure(config: V2ClientConfig): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createSumeClient(config?: V2ClientConfig): SumeClient;
|
|
23
|
+
export declare function configure(config: V2ClientConfig): void;
|
|
24
|
+
export declare function reset(): void;
|