@fluxsave/sdk 0.1.2 → 0.2.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/dist/index.cjs +73 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -4
- package/dist/index.d.ts +60 -4
- package/dist/index.js +72 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22,14 +22,51 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FluxsaveClient: () => FluxsaveClient,
|
|
24
24
|
FluxsaveError: () => FluxsaveError,
|
|
25
|
+
FluxsaveErrorCode: () => FluxsaveErrorCode,
|
|
25
26
|
fileFromBuffer: () => fileFromBuffer
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var FluxsaveErrorCode = {
|
|
30
|
+
FILE_TOO_LARGE: "FILE_TOO_LARGE",
|
|
31
|
+
STORAGE_LIMIT: "STORAGE_LIMIT",
|
|
32
|
+
FILE_COUNT_LIMIT: "FILE_COUNT_LIMIT",
|
|
33
|
+
MIME_TYPE_NOT_ALLOWED: "MIME_TYPE_NOT_ALLOWED",
|
|
34
|
+
COMPRESSION_NOT_ALLOWED: "COMPRESSION_NOT_ALLOWED",
|
|
35
|
+
SUBSCRIPTION_INACTIVE: "SUBSCRIPTION_INACTIVE",
|
|
36
|
+
FOLDER_COUNT_LIMIT: "FOLDER_COUNT_LIMIT",
|
|
37
|
+
EMAIL_ALREADY_REGISTERED: "EMAIL_ALREADY_REGISTERED",
|
|
38
|
+
EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
|
|
39
|
+
INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
|
|
40
|
+
INVALID_OTP: "INVALID_OTP",
|
|
41
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
42
|
+
NOT_FOUND: "NOT_FOUND",
|
|
43
|
+
TIMEOUT: "TIMEOUT",
|
|
44
|
+
UNKNOWN: "UNKNOWN"
|
|
45
|
+
};
|
|
46
|
+
function resolveErrorCode(status, message) {
|
|
47
|
+
const m = message.toLowerCase();
|
|
48
|
+
if (status === 413 && m.includes("storage limit")) return FluxsaveErrorCode.STORAGE_LIMIT;
|
|
49
|
+
if (status === 413) return FluxsaveErrorCode.FILE_TOO_LARGE;
|
|
50
|
+
if (status === 415) return FluxsaveErrorCode.MIME_TYPE_NOT_ALLOWED;
|
|
51
|
+
if (status === 402) return FluxsaveErrorCode.SUBSCRIPTION_INACTIVE;
|
|
52
|
+
if (status === 403 && m.includes("compression")) return FluxsaveErrorCode.COMPRESSION_NOT_ALLOWED;
|
|
53
|
+
if (status === 403 && m.includes("folder")) return FluxsaveErrorCode.FOLDER_COUNT_LIMIT;
|
|
54
|
+
if (status === 403 && (m.includes("file") || m.includes("maximum"))) return FluxsaveErrorCode.FILE_COUNT_LIMIT;
|
|
55
|
+
if (status === 403 && m.includes("email")) return FluxsaveErrorCode.EMAIL_NOT_VERIFIED;
|
|
56
|
+
if (status === 400 && m.includes("already registered")) return FluxsaveErrorCode.EMAIL_ALREADY_REGISTERED;
|
|
57
|
+
if (status === 400 && (m.includes("invalid email or password") || m.includes("invalid credentials"))) return FluxsaveErrorCode.INVALID_CREDENTIALS;
|
|
58
|
+
if (status === 400 && m.includes("otp")) return FluxsaveErrorCode.INVALID_OTP;
|
|
59
|
+
if (status === 401) return FluxsaveErrorCode.UNAUTHORIZED;
|
|
60
|
+
if (status === 404) return FluxsaveErrorCode.NOT_FOUND;
|
|
61
|
+
if (status === 408) return FluxsaveErrorCode.TIMEOUT;
|
|
62
|
+
return FluxsaveErrorCode.UNKNOWN;
|
|
63
|
+
}
|
|
28
64
|
var FluxsaveError = class extends Error {
|
|
29
65
|
constructor(message, status, data) {
|
|
30
66
|
super(message);
|
|
31
67
|
this.name = "FluxsaveError";
|
|
32
68
|
this.status = status;
|
|
69
|
+
this.code = resolveErrorCode(status, message);
|
|
33
70
|
this.data = data;
|
|
34
71
|
}
|
|
35
72
|
};
|
|
@@ -66,8 +103,11 @@ var FluxsaveClient = class {
|
|
|
66
103
|
if (options.name) {
|
|
67
104
|
form.append("name", options.name);
|
|
68
105
|
}
|
|
69
|
-
if (options.
|
|
70
|
-
form.append("
|
|
106
|
+
if (options.compression !== void 0) {
|
|
107
|
+
form.append("compression", options.compression);
|
|
108
|
+
}
|
|
109
|
+
if (options.folderId) {
|
|
110
|
+
form.append("folderId", options.folderId);
|
|
71
111
|
}
|
|
72
112
|
return this.request("/api/v1/files/upload", {
|
|
73
113
|
method: "POST",
|
|
@@ -82,19 +122,41 @@ var FluxsaveClient = class {
|
|
|
82
122
|
if (options.name) {
|
|
83
123
|
form.append("name", options.name);
|
|
84
124
|
}
|
|
85
|
-
if (options.
|
|
86
|
-
form.append("
|
|
125
|
+
if (options.compression !== void 0) {
|
|
126
|
+
form.append("compression", options.compression);
|
|
127
|
+
}
|
|
128
|
+
if (options.folderId) {
|
|
129
|
+
form.append("folderId", options.folderId);
|
|
87
130
|
}
|
|
88
131
|
return this.request("/api/v1/files/upload", {
|
|
89
132
|
method: "POST",
|
|
90
133
|
body: form
|
|
91
134
|
});
|
|
92
135
|
}
|
|
93
|
-
async listFiles() {
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
async listFiles(folderId) {
|
|
137
|
+
const path = folderId ? `/api/v1/files?folderId=${encodeURIComponent(folderId)}` : "/api/v1/files";
|
|
138
|
+
return this.request(path, { method: "GET" });
|
|
139
|
+
}
|
|
140
|
+
async listFolders() {
|
|
141
|
+
return this.request("/api/v1/folders", { method: "GET" });
|
|
142
|
+
}
|
|
143
|
+
async createFolder(name, parentId) {
|
|
144
|
+
return this.request("/api/v1/folders", {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "Content-Type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ name, parentId })
|
|
96
148
|
});
|
|
97
149
|
}
|
|
150
|
+
async renameFolder(folderId, name) {
|
|
151
|
+
return this.request(`/api/v1/folders/${folderId}`, {
|
|
152
|
+
method: "PATCH",
|
|
153
|
+
headers: { "Content-Type": "application/json" },
|
|
154
|
+
body: JSON.stringify({ name })
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async deleteFolder(folderId) {
|
|
158
|
+
return this.request(`/api/v1/folders/${folderId}`, { method: "DELETE" });
|
|
159
|
+
}
|
|
98
160
|
async getFileMetadata(fileId) {
|
|
99
161
|
return this.request(`/api/v1/files/metadata/${fileId}`, {
|
|
100
162
|
method: "GET"
|
|
@@ -106,8 +168,8 @@ var FluxsaveClient = class {
|
|
|
106
168
|
if (options.name) {
|
|
107
169
|
form.append("name", options.name);
|
|
108
170
|
}
|
|
109
|
-
if (options.
|
|
110
|
-
form.append("
|
|
171
|
+
if (options.compression !== void 0) {
|
|
172
|
+
form.append("compression", options.compression);
|
|
111
173
|
}
|
|
112
174
|
return this.request(`/api/v1/files/${fileId}`, {
|
|
113
175
|
method: "PUT",
|
|
@@ -204,7 +266,7 @@ var toArrayBuffer = (buffer) => {
|
|
|
204
266
|
}
|
|
205
267
|
return buffer;
|
|
206
268
|
};
|
|
207
|
-
var fileFromBuffer = (buffer,
|
|
269
|
+
var fileFromBuffer = (buffer, _filename, type = "application/octet-stream") => {
|
|
208
270
|
const data = toArrayBuffer(buffer);
|
|
209
271
|
return new Blob([data], { type });
|
|
210
272
|
};
|
|
@@ -212,6 +274,7 @@ var fileFromBuffer = (buffer, filename, type = "application/octet-stream") => {
|
|
|
212
274
|
0 && (module.exports = {
|
|
213
275
|
FluxsaveClient,
|
|
214
276
|
FluxsaveError,
|
|
277
|
+
FluxsaveErrorCode,
|
|
215
278
|
fileFromBuffer
|
|
216
279
|
});
|
|
217
280
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type ApiSuccess<T> = {\n status: number;\n message: string;\n data: T;\n};\n\nexport type ApiError = {\n status: number;\n message: string;\n data?: unknown;\n};\n\nexport type FileRecord = {\n _id?: string;\n fileId?: string;\n cloudId?: string;\n filename: string;\n originalFilename?: string;\n url: string;\n size: number;\n mimeType: string;\n width?: number;\n height?: number;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Metrics = {\n totalFiles: number;\n totalStorageBytes: number;\n averageFileSize: number;\n storageLimitBytes: number;\n storageRemainingBytes: number;\n storageUsedPercent: number;\n uploadsLast7Days: number;\n latestUploadAt?: string | null;\n byMimeType: Record<string, number>;\n};\n\nexport type FetchLike = typeof fetch;\n\nexport type RetryOptions = {\n retries: number;\n retryDelayMs?: number;\n retryOn?: number[];\n};\n\nexport type FluxsaveClientOptions = {\n baseUrl: string;\n apiKey?: string;\n apiSecret?: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n retry?: RetryOptions;\n};\n\nexport type UploadOptions = {\n name?: string;\n transform?: boolean;\n filename?: string;\n};\n\nexport type TransformOptions = {\n width?: number;\n height?: number;\n format?: string;\n quality?: number | string;\n};\n\nexport class FluxsaveError extends Error {\n status: number;\n data?: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = 'FluxsaveError';\n this.status = status;\n this.data = data;\n }\n}\n\nexport class FluxsaveClient {\n private baseUrl: string;\n private apiKey?: string;\n private apiSecret?: string;\n private fetchImpl: FetchLike;\n private timeoutMs: number;\n private retry: Required<RetryOptions>;\n\n constructor(options: FluxsaveClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.apiSecret = options.apiSecret;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30000;\n this.retry = {\n retries: options.retry?.retries ?? 2,\n retryDelayMs: options.retry?.retryDelayMs ?? 400,\n retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504],\n };\n }\n\n setAuth(apiKey: string, apiSecret: string) {\n this.apiKey = apiKey;\n this.apiSecret = apiSecret;\n }\n\n setTimeout(timeoutMs: number) {\n this.timeoutMs = timeoutMs;\n }\n\n setRetry(options: RetryOptions) {\n this.retry = {\n retries: options.retries,\n retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,\n retryOn: options.retryOn ?? this.retry.retryOn,\n };\n }\n\n async uploadFile(file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async uploadFiles(\n files: Array<{ file: Blob; filename?: string }>,\n options: UploadOptions = {}\n ): Promise<ApiSuccess<FileRecord[]>> {\n const form = new FormData();\n files.forEach((item) => {\n form.append('files', item.file, item.filename || 'file');\n });\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async listFiles(): Promise<ApiSuccess<FileRecord[]>> {\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files', {\n method: 'GET',\n });\n }\n\n async getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>> {\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/metadata/${fileId}`, {\n method: 'GET',\n });\n }\n\n async updateFile(fileId: string, file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/${fileId}`, {\n method: 'PUT',\n body: form,\n });\n }\n\n async deleteFile(fileId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/files/${fileId}`, {\n method: 'DELETE',\n });\n }\n\n async getMetrics(): Promise<ApiSuccess<Metrics>> {\n return this.request<ApiSuccess<Metrics>>('/api/v1/metrics', {\n method: 'GET',\n });\n }\n\n buildFileUrl(fileId: string, options: TransformOptions = {}) {\n const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);\n if (options.width) url.searchParams.set('width', String(options.width));\n if (options.height) url.searchParams.set('height', String(options.height));\n if (options.format) url.searchParams.set('format', options.format);\n if (options.quality !== undefined) url.searchParams.set('quality', String(options.quality));\n return url.toString();\n }\n\n private async request<T>(path: string, init: RequestInit): Promise<T> {\n if (!this.apiKey || !this.apiSecret) {\n throw new FluxsaveError('API key and secret are required', 401);\n }\n\n const headers = new Headers(init.headers || {});\n headers.set('x-api-key', this.apiKey);\n headers.set('x-api-secret', this.apiSecret);\n\n let attempt = 0;\n while (true) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: controller.signal,\n });\n\n const contentType = response.headers.get('content-type') || '';\n const isJson = contentType.includes('application/json');\n const payload = isJson ? await response.json() : await response.text();\n\n if (!response.ok) {\n const message = (payload && (payload.message || payload.error)) || response.statusText;\n const error = new FluxsaveError(message, response.status, payload);\n if (this.shouldRetry(response.status, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n }\n\n return payload as T;\n } catch (error: any) {\n if (error?.name === 'AbortError') {\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw new FluxsaveError('Request timed out', 408);\n }\n\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n\n private shouldRetry(status: number, attempt: number) {\n if (attempt >= this.retry.retries) {\n return false;\n }\n if (status === 0) {\n return true;\n }\n return this.retry.retryOn.includes(status);\n }\n\n private async sleep(delayMs: number) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n}\n\nconst toArrayBuffer = (buffer: ArrayBuffer | Uint8Array) => {\n if (buffer instanceof Uint8Array) {\n const copy = new Uint8Array(buffer.byteLength);\n copy.set(buffer);\n return copy.buffer;\n }\n return buffer;\n};\n\nexport const fileFromBuffer = (\n buffer: ArrayBuffer | Uint8Array,\n filename: string,\n type = 'application/octet-stream'\n) => {\n const data = toArrayBuffer(buffer);\n return new Blob([data], { type });\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type ApiSuccess<T> = {\n status: number;\n message: string;\n data: T;\n};\n\nexport type ApiError = {\n status: number;\n message: string;\n data?: unknown;\n};\n\nexport type FileRecord = {\n _id?: string;\n fileId?: string;\n cloudId?: string;\n filename: string;\n originalFilename?: string;\n url: string;\n size: number;\n mimeType: string;\n width?: number;\n height?: number;\n folderId?: string | null;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Folder = {\n _id: string;\n name: string;\n parentId?: string | null;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Metrics = {\n totalFiles: number;\n totalStorageBytes: number;\n averageFileSize: number;\n storageLimitBytes: number;\n storageRemainingBytes: number;\n storageUsedPercent: number;\n uploadsLast7Days: number;\n latestUploadAt?: string | null;\n byMimeType: Record<string, number>;\n};\n\nexport type FetchLike = typeof fetch;\n\nexport type RetryOptions = {\n retries: number;\n retryDelayMs?: number;\n retryOn?: number[];\n};\n\nexport type FluxsaveClientOptions = {\n baseUrl: string;\n apiKey?: string;\n apiSecret?: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n retry?: RetryOptions;\n};\n\nexport type CompressionLevel = 'none' | 'low' | 'medium' | 'high';\n\nexport type UploadOptions = {\n name?: string;\n compression?: CompressionLevel;\n filename?: string;\n folderId?: string;\n};\n\nexport type TransformOptions = {\n width?: number;\n height?: number;\n format?: string;\n quality?: number | string;\n};\n\n/**\n * Error codes returned by the Fluxsave API.\n *\n * Upload / plan-limit errors\n * FILE_TOO_LARGE – 413 file exceeds plan's maxFileSizeBytes\n * STORAGE_LIMIT – 413 total storage quota exceeded\n * FILE_COUNT_LIMIT – 403 plan's maxFilesCount reached\n * MIME_TYPE_NOT_ALLOWED – 415 file type blocked by plan\n * COMPRESSION_NOT_ALLOWED – 403 compression level not permitted by plan\n * SUBSCRIPTION_INACTIVE – 402 user subscription is not active\n * FOLDER_COUNT_LIMIT – 403 plan's maxFoldersCount reached\n *\n * Auth errors\n * EMAIL_ALREADY_REGISTERED – 400 duplicate email on register\n * EMAIL_NOT_VERIFIED – 403 login attempted before verifying email\n * INVALID_CREDENTIALS – 400 wrong email or password\n * INVALID_OTP – 400 bad/expired verification code\n *\n * Generic\n * UNAUTHORIZED – 401\n * NOT_FOUND – 404\n * TIMEOUT – 408\n */\nexport const FluxsaveErrorCode = {\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n STORAGE_LIMIT: 'STORAGE_LIMIT',\n FILE_COUNT_LIMIT: 'FILE_COUNT_LIMIT',\n MIME_TYPE_NOT_ALLOWED: 'MIME_TYPE_NOT_ALLOWED',\n COMPRESSION_NOT_ALLOWED: 'COMPRESSION_NOT_ALLOWED',\n SUBSCRIPTION_INACTIVE: 'SUBSCRIPTION_INACTIVE',\n FOLDER_COUNT_LIMIT: 'FOLDER_COUNT_LIMIT',\n EMAIL_ALREADY_REGISTERED: 'EMAIL_ALREADY_REGISTERED',\n EMAIL_NOT_VERIFIED: 'EMAIL_NOT_VERIFIED',\n INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',\n INVALID_OTP: 'INVALID_OTP',\n UNAUTHORIZED: 'UNAUTHORIZED',\n NOT_FOUND: 'NOT_FOUND',\n TIMEOUT: 'TIMEOUT',\n UNKNOWN: 'UNKNOWN',\n} as const;\n\nexport type FluxsaveErrorCode = typeof FluxsaveErrorCode[keyof typeof FluxsaveErrorCode];\n\nfunction resolveErrorCode(status: number, message: string): FluxsaveErrorCode {\n const m = message.toLowerCase();\n if (status === 413 && m.includes('storage limit')) return FluxsaveErrorCode.STORAGE_LIMIT;\n if (status === 413) return FluxsaveErrorCode.FILE_TOO_LARGE;\n if (status === 415) return FluxsaveErrorCode.MIME_TYPE_NOT_ALLOWED;\n if (status === 402) return FluxsaveErrorCode.SUBSCRIPTION_INACTIVE;\n if (status === 403 && m.includes('compression')) return FluxsaveErrorCode.COMPRESSION_NOT_ALLOWED;\n if (status === 403 && m.includes('folder')) return FluxsaveErrorCode.FOLDER_COUNT_LIMIT;\n if (status === 403 && (m.includes('file') || m.includes('maximum'))) return FluxsaveErrorCode.FILE_COUNT_LIMIT;\n if (status === 403 && m.includes('email')) return FluxsaveErrorCode.EMAIL_NOT_VERIFIED;\n if (status === 400 && m.includes('already registered')) return FluxsaveErrorCode.EMAIL_ALREADY_REGISTERED;\n if (status === 400 && (m.includes('invalid email or password') || m.includes('invalid credentials'))) return FluxsaveErrorCode.INVALID_CREDENTIALS;\n if (status === 400 && m.includes('otp')) return FluxsaveErrorCode.INVALID_OTP;\n if (status === 401) return FluxsaveErrorCode.UNAUTHORIZED;\n if (status === 404) return FluxsaveErrorCode.NOT_FOUND;\n if (status === 408) return FluxsaveErrorCode.TIMEOUT;\n return FluxsaveErrorCode.UNKNOWN;\n}\n\nexport class FluxsaveError extends Error {\n status: number;\n code: FluxsaveErrorCode;\n data?: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = 'FluxsaveError';\n this.status = status;\n this.code = resolveErrorCode(status, message);\n this.data = data;\n }\n}\n\nexport class FluxsaveClient {\n private baseUrl: string;\n private apiKey?: string;\n private apiSecret?: string;\n private fetchImpl: FetchLike;\n private timeoutMs: number;\n private retry: Required<RetryOptions>;\n\n constructor(options: FluxsaveClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.apiSecret = options.apiSecret;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30000;\n this.retry = {\n retries: options.retry?.retries ?? 2,\n retryDelayMs: options.retry?.retryDelayMs ?? 400,\n retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504],\n };\n }\n\n setAuth(apiKey: string, apiSecret: string) {\n this.apiKey = apiKey;\n this.apiSecret = apiSecret;\n }\n\n setTimeout(timeoutMs: number) {\n this.timeoutMs = timeoutMs;\n }\n\n setRetry(options: RetryOptions) {\n this.retry = {\n retries: options.retries,\n retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,\n retryOn: options.retryOn ?? this.retry.retryOn,\n };\n }\n\n async uploadFile(file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n if (options.folderId) {\n form.append('folderId', options.folderId);\n }\n\n return this.request<ApiSuccess<FileRecord>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async uploadFiles(\n files: Array<{ file: Blob; filename?: string }>,\n options: UploadOptions = {}\n ): Promise<ApiSuccess<FileRecord[]>> {\n const form = new FormData();\n files.forEach((item) => {\n form.append('files', item.file, item.filename || 'file');\n });\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n if (options.folderId) {\n form.append('folderId', options.folderId);\n }\n\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async listFiles(folderId?: string): Promise<ApiSuccess<FileRecord[]>> {\n const path = folderId ? `/api/v1/files?folderId=${encodeURIComponent(folderId)}` : '/api/v1/files';\n return this.request<ApiSuccess<FileRecord[]>>(path, { method: 'GET' });\n }\n\n async listFolders(): Promise<ApiSuccess<Folder[]>> {\n return this.request<ApiSuccess<Folder[]>>('/api/v1/folders', { method: 'GET' });\n }\n\n async createFolder(name: string, parentId?: string): Promise<ApiSuccess<Folder>> {\n return this.request<ApiSuccess<Folder>>('/api/v1/folders', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, parentId }),\n });\n }\n\n async renameFolder(folderId: string, name: string): Promise<ApiSuccess<Folder>> {\n return this.request<ApiSuccess<Folder>>(`/api/v1/folders/${folderId}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n });\n }\n\n async deleteFolder(folderId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/folders/${folderId}`, { method: 'DELETE' });\n }\n\n async getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>> {\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/metadata/${fileId}`, {\n method: 'GET',\n });\n }\n\n async updateFile(fileId: string, file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/${fileId}`, {\n method: 'PUT',\n body: form,\n });\n }\n\n async deleteFile(fileId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/files/${fileId}`, {\n method: 'DELETE',\n });\n }\n\n async getMetrics(): Promise<ApiSuccess<Metrics>> {\n return this.request<ApiSuccess<Metrics>>('/api/v1/metrics', {\n method: 'GET',\n });\n }\n\n buildFileUrl(fileId: string, options: TransformOptions = {}) {\n const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);\n if (options.width) url.searchParams.set('width', String(options.width));\n if (options.height) url.searchParams.set('height', String(options.height));\n if (options.format) url.searchParams.set('format', options.format);\n if (options.quality !== undefined) url.searchParams.set('quality', String(options.quality));\n return url.toString();\n }\n\n private async request<T>(path: string, init: RequestInit): Promise<T> {\n if (!this.apiKey || !this.apiSecret) {\n throw new FluxsaveError('API key and secret are required', 401);\n }\n\n const headers = new Headers(init.headers || {});\n headers.set('x-api-key', this.apiKey);\n headers.set('x-api-secret', this.apiSecret);\n\n let attempt = 0;\n while (true) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: controller.signal,\n });\n\n const contentType = response.headers.get('content-type') || '';\n const isJson = contentType.includes('application/json');\n const payload = isJson ? await response.json() : await response.text();\n\n if (!response.ok) {\n const message = (payload && (payload.message || payload.error)) || response.statusText;\n const error = new FluxsaveError(message, response.status, payload);\n if (this.shouldRetry(response.status, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n }\n\n return payload as T;\n } catch (error: any) {\n if (error?.name === 'AbortError') {\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw new FluxsaveError('Request timed out', 408);\n }\n\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n\n private shouldRetry(status: number, attempt: number) {\n if (attempt >= this.retry.retries) {\n return false;\n }\n if (status === 0) {\n return true;\n }\n return this.retry.retryOn.includes(status);\n }\n\n private async sleep(delayMs: number) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n}\n\nconst toArrayBuffer = (buffer: ArrayBuffer | Uint8Array) => {\n if (buffer instanceof Uint8Array) {\n const copy = new Uint8Array(buffer.byteLength);\n copy.set(buffer);\n return copy.buffer;\n }\n return buffer;\n};\n\nexport const fileFromBuffer = (\n buffer: ArrayBuffer | Uint8Array,\n _filename: string,\n type = 'application/octet-stream'\n) => {\n const data = toArrayBuffer(buffer);\n return new Blob([data], { type });\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -19,6 +19,14 @@ type FileRecord = {
|
|
|
19
19
|
mimeType: string;
|
|
20
20
|
width?: number;
|
|
21
21
|
height?: number;
|
|
22
|
+
folderId?: string | null;
|
|
23
|
+
createdAt?: string;
|
|
24
|
+
updatedAt?: string;
|
|
25
|
+
};
|
|
26
|
+
type Folder = {
|
|
27
|
+
_id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
parentId?: string | null;
|
|
22
30
|
createdAt?: string;
|
|
23
31
|
updatedAt?: string;
|
|
24
32
|
};
|
|
@@ -47,10 +55,12 @@ type FluxsaveClientOptions = {
|
|
|
47
55
|
timeoutMs?: number;
|
|
48
56
|
retry?: RetryOptions;
|
|
49
57
|
};
|
|
58
|
+
type CompressionLevel = 'none' | 'low' | 'medium' | 'high';
|
|
50
59
|
type UploadOptions = {
|
|
51
60
|
name?: string;
|
|
52
|
-
|
|
61
|
+
compression?: CompressionLevel;
|
|
53
62
|
filename?: string;
|
|
63
|
+
folderId?: string;
|
|
54
64
|
};
|
|
55
65
|
type TransformOptions = {
|
|
56
66
|
width?: number;
|
|
@@ -58,8 +68,50 @@ type TransformOptions = {
|
|
|
58
68
|
format?: string;
|
|
59
69
|
quality?: number | string;
|
|
60
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Error codes returned by the Fluxsave API.
|
|
73
|
+
*
|
|
74
|
+
* Upload / plan-limit errors
|
|
75
|
+
* FILE_TOO_LARGE – 413 file exceeds plan's maxFileSizeBytes
|
|
76
|
+
* STORAGE_LIMIT – 413 total storage quota exceeded
|
|
77
|
+
* FILE_COUNT_LIMIT – 403 plan's maxFilesCount reached
|
|
78
|
+
* MIME_TYPE_NOT_ALLOWED – 415 file type blocked by plan
|
|
79
|
+
* COMPRESSION_NOT_ALLOWED – 403 compression level not permitted by plan
|
|
80
|
+
* SUBSCRIPTION_INACTIVE – 402 user subscription is not active
|
|
81
|
+
* FOLDER_COUNT_LIMIT – 403 plan's maxFoldersCount reached
|
|
82
|
+
*
|
|
83
|
+
* Auth errors
|
|
84
|
+
* EMAIL_ALREADY_REGISTERED – 400 duplicate email on register
|
|
85
|
+
* EMAIL_NOT_VERIFIED – 403 login attempted before verifying email
|
|
86
|
+
* INVALID_CREDENTIALS – 400 wrong email or password
|
|
87
|
+
* INVALID_OTP – 400 bad/expired verification code
|
|
88
|
+
*
|
|
89
|
+
* Generic
|
|
90
|
+
* UNAUTHORIZED – 401
|
|
91
|
+
* NOT_FOUND – 404
|
|
92
|
+
* TIMEOUT – 408
|
|
93
|
+
*/
|
|
94
|
+
declare const FluxsaveErrorCode: {
|
|
95
|
+
readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
|
|
96
|
+
readonly STORAGE_LIMIT: "STORAGE_LIMIT";
|
|
97
|
+
readonly FILE_COUNT_LIMIT: "FILE_COUNT_LIMIT";
|
|
98
|
+
readonly MIME_TYPE_NOT_ALLOWED: "MIME_TYPE_NOT_ALLOWED";
|
|
99
|
+
readonly COMPRESSION_NOT_ALLOWED: "COMPRESSION_NOT_ALLOWED";
|
|
100
|
+
readonly SUBSCRIPTION_INACTIVE: "SUBSCRIPTION_INACTIVE";
|
|
101
|
+
readonly FOLDER_COUNT_LIMIT: "FOLDER_COUNT_LIMIT";
|
|
102
|
+
readonly EMAIL_ALREADY_REGISTERED: "EMAIL_ALREADY_REGISTERED";
|
|
103
|
+
readonly EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED";
|
|
104
|
+
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
105
|
+
readonly INVALID_OTP: "INVALID_OTP";
|
|
106
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
107
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
108
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
109
|
+
readonly UNKNOWN: "UNKNOWN";
|
|
110
|
+
};
|
|
111
|
+
type FluxsaveErrorCode = typeof FluxsaveErrorCode[keyof typeof FluxsaveErrorCode];
|
|
61
112
|
declare class FluxsaveError extends Error {
|
|
62
113
|
status: number;
|
|
114
|
+
code: FluxsaveErrorCode;
|
|
63
115
|
data?: unknown;
|
|
64
116
|
constructor(message: string, status: number, data?: unknown);
|
|
65
117
|
}
|
|
@@ -79,7 +131,11 @@ declare class FluxsaveClient {
|
|
|
79
131
|
file: Blob;
|
|
80
132
|
filename?: string;
|
|
81
133
|
}>, options?: UploadOptions): Promise<ApiSuccess<FileRecord[]>>;
|
|
82
|
-
listFiles(): Promise<ApiSuccess<FileRecord[]>>;
|
|
134
|
+
listFiles(folderId?: string): Promise<ApiSuccess<FileRecord[]>>;
|
|
135
|
+
listFolders(): Promise<ApiSuccess<Folder[]>>;
|
|
136
|
+
createFolder(name: string, parentId?: string): Promise<ApiSuccess<Folder>>;
|
|
137
|
+
renameFolder(folderId: string, name: string): Promise<ApiSuccess<Folder>>;
|
|
138
|
+
deleteFolder(folderId: string): Promise<ApiSuccess<null>>;
|
|
83
139
|
getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>>;
|
|
84
140
|
updateFile(fileId: string, file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
85
141
|
deleteFile(fileId: string): Promise<ApiSuccess<null>>;
|
|
@@ -89,6 +145,6 @@ declare class FluxsaveClient {
|
|
|
89
145
|
private shouldRetry;
|
|
90
146
|
private sleep;
|
|
91
147
|
}
|
|
92
|
-
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array,
|
|
148
|
+
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array, _filename: string, type?: string) => Blob;
|
|
93
149
|
|
|
94
|
-
export { type ApiError, type ApiSuccess, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
|
150
|
+
export { type ApiError, type ApiSuccess, type CompressionLevel, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, FluxsaveErrorCode, type Folder, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ type FileRecord = {
|
|
|
19
19
|
mimeType: string;
|
|
20
20
|
width?: number;
|
|
21
21
|
height?: number;
|
|
22
|
+
folderId?: string | null;
|
|
23
|
+
createdAt?: string;
|
|
24
|
+
updatedAt?: string;
|
|
25
|
+
};
|
|
26
|
+
type Folder = {
|
|
27
|
+
_id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
parentId?: string | null;
|
|
22
30
|
createdAt?: string;
|
|
23
31
|
updatedAt?: string;
|
|
24
32
|
};
|
|
@@ -47,10 +55,12 @@ type FluxsaveClientOptions = {
|
|
|
47
55
|
timeoutMs?: number;
|
|
48
56
|
retry?: RetryOptions;
|
|
49
57
|
};
|
|
58
|
+
type CompressionLevel = 'none' | 'low' | 'medium' | 'high';
|
|
50
59
|
type UploadOptions = {
|
|
51
60
|
name?: string;
|
|
52
|
-
|
|
61
|
+
compression?: CompressionLevel;
|
|
53
62
|
filename?: string;
|
|
63
|
+
folderId?: string;
|
|
54
64
|
};
|
|
55
65
|
type TransformOptions = {
|
|
56
66
|
width?: number;
|
|
@@ -58,8 +68,50 @@ type TransformOptions = {
|
|
|
58
68
|
format?: string;
|
|
59
69
|
quality?: number | string;
|
|
60
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Error codes returned by the Fluxsave API.
|
|
73
|
+
*
|
|
74
|
+
* Upload / plan-limit errors
|
|
75
|
+
* FILE_TOO_LARGE – 413 file exceeds plan's maxFileSizeBytes
|
|
76
|
+
* STORAGE_LIMIT – 413 total storage quota exceeded
|
|
77
|
+
* FILE_COUNT_LIMIT – 403 plan's maxFilesCount reached
|
|
78
|
+
* MIME_TYPE_NOT_ALLOWED – 415 file type blocked by plan
|
|
79
|
+
* COMPRESSION_NOT_ALLOWED – 403 compression level not permitted by plan
|
|
80
|
+
* SUBSCRIPTION_INACTIVE – 402 user subscription is not active
|
|
81
|
+
* FOLDER_COUNT_LIMIT – 403 plan's maxFoldersCount reached
|
|
82
|
+
*
|
|
83
|
+
* Auth errors
|
|
84
|
+
* EMAIL_ALREADY_REGISTERED – 400 duplicate email on register
|
|
85
|
+
* EMAIL_NOT_VERIFIED – 403 login attempted before verifying email
|
|
86
|
+
* INVALID_CREDENTIALS – 400 wrong email or password
|
|
87
|
+
* INVALID_OTP – 400 bad/expired verification code
|
|
88
|
+
*
|
|
89
|
+
* Generic
|
|
90
|
+
* UNAUTHORIZED – 401
|
|
91
|
+
* NOT_FOUND – 404
|
|
92
|
+
* TIMEOUT – 408
|
|
93
|
+
*/
|
|
94
|
+
declare const FluxsaveErrorCode: {
|
|
95
|
+
readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
|
|
96
|
+
readonly STORAGE_LIMIT: "STORAGE_LIMIT";
|
|
97
|
+
readonly FILE_COUNT_LIMIT: "FILE_COUNT_LIMIT";
|
|
98
|
+
readonly MIME_TYPE_NOT_ALLOWED: "MIME_TYPE_NOT_ALLOWED";
|
|
99
|
+
readonly COMPRESSION_NOT_ALLOWED: "COMPRESSION_NOT_ALLOWED";
|
|
100
|
+
readonly SUBSCRIPTION_INACTIVE: "SUBSCRIPTION_INACTIVE";
|
|
101
|
+
readonly FOLDER_COUNT_LIMIT: "FOLDER_COUNT_LIMIT";
|
|
102
|
+
readonly EMAIL_ALREADY_REGISTERED: "EMAIL_ALREADY_REGISTERED";
|
|
103
|
+
readonly EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED";
|
|
104
|
+
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
105
|
+
readonly INVALID_OTP: "INVALID_OTP";
|
|
106
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
107
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
108
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
109
|
+
readonly UNKNOWN: "UNKNOWN";
|
|
110
|
+
};
|
|
111
|
+
type FluxsaveErrorCode = typeof FluxsaveErrorCode[keyof typeof FluxsaveErrorCode];
|
|
61
112
|
declare class FluxsaveError extends Error {
|
|
62
113
|
status: number;
|
|
114
|
+
code: FluxsaveErrorCode;
|
|
63
115
|
data?: unknown;
|
|
64
116
|
constructor(message: string, status: number, data?: unknown);
|
|
65
117
|
}
|
|
@@ -79,7 +131,11 @@ declare class FluxsaveClient {
|
|
|
79
131
|
file: Blob;
|
|
80
132
|
filename?: string;
|
|
81
133
|
}>, options?: UploadOptions): Promise<ApiSuccess<FileRecord[]>>;
|
|
82
|
-
listFiles(): Promise<ApiSuccess<FileRecord[]>>;
|
|
134
|
+
listFiles(folderId?: string): Promise<ApiSuccess<FileRecord[]>>;
|
|
135
|
+
listFolders(): Promise<ApiSuccess<Folder[]>>;
|
|
136
|
+
createFolder(name: string, parentId?: string): Promise<ApiSuccess<Folder>>;
|
|
137
|
+
renameFolder(folderId: string, name: string): Promise<ApiSuccess<Folder>>;
|
|
138
|
+
deleteFolder(folderId: string): Promise<ApiSuccess<null>>;
|
|
83
139
|
getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>>;
|
|
84
140
|
updateFile(fileId: string, file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
85
141
|
deleteFile(fileId: string): Promise<ApiSuccess<null>>;
|
|
@@ -89,6 +145,6 @@ declare class FluxsaveClient {
|
|
|
89
145
|
private shouldRetry;
|
|
90
146
|
private sleep;
|
|
91
147
|
}
|
|
92
|
-
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array,
|
|
148
|
+
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array, _filename: string, type?: string) => Blob;
|
|
93
149
|
|
|
94
|
-
export { type ApiError, type ApiSuccess, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
|
150
|
+
export { type ApiError, type ApiSuccess, type CompressionLevel, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, FluxsaveErrorCode, type Folder, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var FluxsaveErrorCode = {
|
|
3
|
+
FILE_TOO_LARGE: "FILE_TOO_LARGE",
|
|
4
|
+
STORAGE_LIMIT: "STORAGE_LIMIT",
|
|
5
|
+
FILE_COUNT_LIMIT: "FILE_COUNT_LIMIT",
|
|
6
|
+
MIME_TYPE_NOT_ALLOWED: "MIME_TYPE_NOT_ALLOWED",
|
|
7
|
+
COMPRESSION_NOT_ALLOWED: "COMPRESSION_NOT_ALLOWED",
|
|
8
|
+
SUBSCRIPTION_INACTIVE: "SUBSCRIPTION_INACTIVE",
|
|
9
|
+
FOLDER_COUNT_LIMIT: "FOLDER_COUNT_LIMIT",
|
|
10
|
+
EMAIL_ALREADY_REGISTERED: "EMAIL_ALREADY_REGISTERED",
|
|
11
|
+
EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
|
|
12
|
+
INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
|
|
13
|
+
INVALID_OTP: "INVALID_OTP",
|
|
14
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
15
|
+
NOT_FOUND: "NOT_FOUND",
|
|
16
|
+
TIMEOUT: "TIMEOUT",
|
|
17
|
+
UNKNOWN: "UNKNOWN"
|
|
18
|
+
};
|
|
19
|
+
function resolveErrorCode(status, message) {
|
|
20
|
+
const m = message.toLowerCase();
|
|
21
|
+
if (status === 413 && m.includes("storage limit")) return FluxsaveErrorCode.STORAGE_LIMIT;
|
|
22
|
+
if (status === 413) return FluxsaveErrorCode.FILE_TOO_LARGE;
|
|
23
|
+
if (status === 415) return FluxsaveErrorCode.MIME_TYPE_NOT_ALLOWED;
|
|
24
|
+
if (status === 402) return FluxsaveErrorCode.SUBSCRIPTION_INACTIVE;
|
|
25
|
+
if (status === 403 && m.includes("compression")) return FluxsaveErrorCode.COMPRESSION_NOT_ALLOWED;
|
|
26
|
+
if (status === 403 && m.includes("folder")) return FluxsaveErrorCode.FOLDER_COUNT_LIMIT;
|
|
27
|
+
if (status === 403 && (m.includes("file") || m.includes("maximum"))) return FluxsaveErrorCode.FILE_COUNT_LIMIT;
|
|
28
|
+
if (status === 403 && m.includes("email")) return FluxsaveErrorCode.EMAIL_NOT_VERIFIED;
|
|
29
|
+
if (status === 400 && m.includes("already registered")) return FluxsaveErrorCode.EMAIL_ALREADY_REGISTERED;
|
|
30
|
+
if (status === 400 && (m.includes("invalid email or password") || m.includes("invalid credentials"))) return FluxsaveErrorCode.INVALID_CREDENTIALS;
|
|
31
|
+
if (status === 400 && m.includes("otp")) return FluxsaveErrorCode.INVALID_OTP;
|
|
32
|
+
if (status === 401) return FluxsaveErrorCode.UNAUTHORIZED;
|
|
33
|
+
if (status === 404) return FluxsaveErrorCode.NOT_FOUND;
|
|
34
|
+
if (status === 408) return FluxsaveErrorCode.TIMEOUT;
|
|
35
|
+
return FluxsaveErrorCode.UNKNOWN;
|
|
36
|
+
}
|
|
2
37
|
var FluxsaveError = class extends Error {
|
|
3
38
|
constructor(message, status, data) {
|
|
4
39
|
super(message);
|
|
5
40
|
this.name = "FluxsaveError";
|
|
6
41
|
this.status = status;
|
|
42
|
+
this.code = resolveErrorCode(status, message);
|
|
7
43
|
this.data = data;
|
|
8
44
|
}
|
|
9
45
|
};
|
|
@@ -40,8 +76,11 @@ var FluxsaveClient = class {
|
|
|
40
76
|
if (options.name) {
|
|
41
77
|
form.append("name", options.name);
|
|
42
78
|
}
|
|
43
|
-
if (options.
|
|
44
|
-
form.append("
|
|
79
|
+
if (options.compression !== void 0) {
|
|
80
|
+
form.append("compression", options.compression);
|
|
81
|
+
}
|
|
82
|
+
if (options.folderId) {
|
|
83
|
+
form.append("folderId", options.folderId);
|
|
45
84
|
}
|
|
46
85
|
return this.request("/api/v1/files/upload", {
|
|
47
86
|
method: "POST",
|
|
@@ -56,19 +95,41 @@ var FluxsaveClient = class {
|
|
|
56
95
|
if (options.name) {
|
|
57
96
|
form.append("name", options.name);
|
|
58
97
|
}
|
|
59
|
-
if (options.
|
|
60
|
-
form.append("
|
|
98
|
+
if (options.compression !== void 0) {
|
|
99
|
+
form.append("compression", options.compression);
|
|
100
|
+
}
|
|
101
|
+
if (options.folderId) {
|
|
102
|
+
form.append("folderId", options.folderId);
|
|
61
103
|
}
|
|
62
104
|
return this.request("/api/v1/files/upload", {
|
|
63
105
|
method: "POST",
|
|
64
106
|
body: form
|
|
65
107
|
});
|
|
66
108
|
}
|
|
67
|
-
async listFiles() {
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
async listFiles(folderId) {
|
|
110
|
+
const path = folderId ? `/api/v1/files?folderId=${encodeURIComponent(folderId)}` : "/api/v1/files";
|
|
111
|
+
return this.request(path, { method: "GET" });
|
|
112
|
+
}
|
|
113
|
+
async listFolders() {
|
|
114
|
+
return this.request("/api/v1/folders", { method: "GET" });
|
|
115
|
+
}
|
|
116
|
+
async createFolder(name, parentId) {
|
|
117
|
+
return this.request("/api/v1/folders", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: { "Content-Type": "application/json" },
|
|
120
|
+
body: JSON.stringify({ name, parentId })
|
|
70
121
|
});
|
|
71
122
|
}
|
|
123
|
+
async renameFolder(folderId, name) {
|
|
124
|
+
return this.request(`/api/v1/folders/${folderId}`, {
|
|
125
|
+
method: "PATCH",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({ name })
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async deleteFolder(folderId) {
|
|
131
|
+
return this.request(`/api/v1/folders/${folderId}`, { method: "DELETE" });
|
|
132
|
+
}
|
|
72
133
|
async getFileMetadata(fileId) {
|
|
73
134
|
return this.request(`/api/v1/files/metadata/${fileId}`, {
|
|
74
135
|
method: "GET"
|
|
@@ -80,8 +141,8 @@ var FluxsaveClient = class {
|
|
|
80
141
|
if (options.name) {
|
|
81
142
|
form.append("name", options.name);
|
|
82
143
|
}
|
|
83
|
-
if (options.
|
|
84
|
-
form.append("
|
|
144
|
+
if (options.compression !== void 0) {
|
|
145
|
+
form.append("compression", options.compression);
|
|
85
146
|
}
|
|
86
147
|
return this.request(`/api/v1/files/${fileId}`, {
|
|
87
148
|
method: "PUT",
|
|
@@ -178,13 +239,14 @@ var toArrayBuffer = (buffer) => {
|
|
|
178
239
|
}
|
|
179
240
|
return buffer;
|
|
180
241
|
};
|
|
181
|
-
var fileFromBuffer = (buffer,
|
|
242
|
+
var fileFromBuffer = (buffer, _filename, type = "application/octet-stream") => {
|
|
182
243
|
const data = toArrayBuffer(buffer);
|
|
183
244
|
return new Blob([data], { type });
|
|
184
245
|
};
|
|
185
246
|
export {
|
|
186
247
|
FluxsaveClient,
|
|
187
248
|
FluxsaveError,
|
|
249
|
+
FluxsaveErrorCode,
|
|
188
250
|
fileFromBuffer
|
|
189
251
|
};
|
|
190
252
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type ApiSuccess<T> = {\n status: number;\n message: string;\n data: T;\n};\n\nexport type ApiError = {\n status: number;\n message: string;\n data?: unknown;\n};\n\nexport type FileRecord = {\n _id?: string;\n fileId?: string;\n cloudId?: string;\n filename: string;\n originalFilename?: string;\n url: string;\n size: number;\n mimeType: string;\n width?: number;\n height?: number;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Metrics = {\n totalFiles: number;\n totalStorageBytes: number;\n averageFileSize: number;\n storageLimitBytes: number;\n storageRemainingBytes: number;\n storageUsedPercent: number;\n uploadsLast7Days: number;\n latestUploadAt?: string | null;\n byMimeType: Record<string, number>;\n};\n\nexport type FetchLike = typeof fetch;\n\nexport type RetryOptions = {\n retries: number;\n retryDelayMs?: number;\n retryOn?: number[];\n};\n\nexport type FluxsaveClientOptions = {\n baseUrl: string;\n apiKey?: string;\n apiSecret?: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n retry?: RetryOptions;\n};\n\nexport type UploadOptions = {\n name?: string;\n transform?: boolean;\n filename?: string;\n};\n\nexport type TransformOptions = {\n width?: number;\n height?: number;\n format?: string;\n quality?: number | string;\n};\n\nexport class FluxsaveError extends Error {\n status: number;\n data?: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = 'FluxsaveError';\n this.status = status;\n this.data = data;\n }\n}\n\nexport class FluxsaveClient {\n private baseUrl: string;\n private apiKey?: string;\n private apiSecret?: string;\n private fetchImpl: FetchLike;\n private timeoutMs: number;\n private retry: Required<RetryOptions>;\n\n constructor(options: FluxsaveClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.apiSecret = options.apiSecret;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30000;\n this.retry = {\n retries: options.retry?.retries ?? 2,\n retryDelayMs: options.retry?.retryDelayMs ?? 400,\n retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504],\n };\n }\n\n setAuth(apiKey: string, apiSecret: string) {\n this.apiKey = apiKey;\n this.apiSecret = apiSecret;\n }\n\n setTimeout(timeoutMs: number) {\n this.timeoutMs = timeoutMs;\n }\n\n setRetry(options: RetryOptions) {\n this.retry = {\n retries: options.retries,\n retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,\n retryOn: options.retryOn ?? this.retry.retryOn,\n };\n }\n\n async uploadFile(file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async uploadFiles(\n files: Array<{ file: Blob; filename?: string }>,\n options: UploadOptions = {}\n ): Promise<ApiSuccess<FileRecord[]>> {\n const form = new FormData();\n files.forEach((item) => {\n form.append('files', item.file, item.filename || 'file');\n });\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async listFiles(): Promise<ApiSuccess<FileRecord[]>> {\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files', {\n method: 'GET',\n });\n }\n\n async getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>> {\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/metadata/${fileId}`, {\n method: 'GET',\n });\n }\n\n async updateFile(fileId: string, file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.transform !== undefined) {\n form.append('transform', String(options.transform));\n }\n\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/${fileId}`, {\n method: 'PUT',\n body: form,\n });\n }\n\n async deleteFile(fileId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/files/${fileId}`, {\n method: 'DELETE',\n });\n }\n\n async getMetrics(): Promise<ApiSuccess<Metrics>> {\n return this.request<ApiSuccess<Metrics>>('/api/v1/metrics', {\n method: 'GET',\n });\n }\n\n buildFileUrl(fileId: string, options: TransformOptions = {}) {\n const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);\n if (options.width) url.searchParams.set('width', String(options.width));\n if (options.height) url.searchParams.set('height', String(options.height));\n if (options.format) url.searchParams.set('format', options.format);\n if (options.quality !== undefined) url.searchParams.set('quality', String(options.quality));\n return url.toString();\n }\n\n private async request<T>(path: string, init: RequestInit): Promise<T> {\n if (!this.apiKey || !this.apiSecret) {\n throw new FluxsaveError('API key and secret are required', 401);\n }\n\n const headers = new Headers(init.headers || {});\n headers.set('x-api-key', this.apiKey);\n headers.set('x-api-secret', this.apiSecret);\n\n let attempt = 0;\n while (true) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: controller.signal,\n });\n\n const contentType = response.headers.get('content-type') || '';\n const isJson = contentType.includes('application/json');\n const payload = isJson ? await response.json() : await response.text();\n\n if (!response.ok) {\n const message = (payload && (payload.message || payload.error)) || response.statusText;\n const error = new FluxsaveError(message, response.status, payload);\n if (this.shouldRetry(response.status, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n }\n\n return payload as T;\n } catch (error: any) {\n if (error?.name === 'AbortError') {\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw new FluxsaveError('Request timed out', 408);\n }\n\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n\n private shouldRetry(status: number, attempt: number) {\n if (attempt >= this.retry.retries) {\n return false;\n }\n if (status === 0) {\n return true;\n }\n return this.retry.retryOn.includes(status);\n }\n\n private async sleep(delayMs: number) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n}\n\nconst toArrayBuffer = (buffer: ArrayBuffer | Uint8Array) => {\n if (buffer instanceof Uint8Array) {\n const copy = new Uint8Array(buffer.byteLength);\n copy.set(buffer);\n return copy.buffer;\n }\n return buffer;\n};\n\nexport const fileFromBuffer = (\n buffer: ArrayBuffer | Uint8Array,\n filename: string,\n type = 'application/octet-stream'\n) => {\n const data = toArrayBuffer(buffer);\n return new Blob([data], { type });\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type ApiSuccess<T> = {\n status: number;\n message: string;\n data: T;\n};\n\nexport type ApiError = {\n status: number;\n message: string;\n data?: unknown;\n};\n\nexport type FileRecord = {\n _id?: string;\n fileId?: string;\n cloudId?: string;\n filename: string;\n originalFilename?: string;\n url: string;\n size: number;\n mimeType: string;\n width?: number;\n height?: number;\n folderId?: string | null;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Folder = {\n _id: string;\n name: string;\n parentId?: string | null;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type Metrics = {\n totalFiles: number;\n totalStorageBytes: number;\n averageFileSize: number;\n storageLimitBytes: number;\n storageRemainingBytes: number;\n storageUsedPercent: number;\n uploadsLast7Days: number;\n latestUploadAt?: string | null;\n byMimeType: Record<string, number>;\n};\n\nexport type FetchLike = typeof fetch;\n\nexport type RetryOptions = {\n retries: number;\n retryDelayMs?: number;\n retryOn?: number[];\n};\n\nexport type FluxsaveClientOptions = {\n baseUrl: string;\n apiKey?: string;\n apiSecret?: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n retry?: RetryOptions;\n};\n\nexport type CompressionLevel = 'none' | 'low' | 'medium' | 'high';\n\nexport type UploadOptions = {\n name?: string;\n compression?: CompressionLevel;\n filename?: string;\n folderId?: string;\n};\n\nexport type TransformOptions = {\n width?: number;\n height?: number;\n format?: string;\n quality?: number | string;\n};\n\n/**\n * Error codes returned by the Fluxsave API.\n *\n * Upload / plan-limit errors\n * FILE_TOO_LARGE – 413 file exceeds plan's maxFileSizeBytes\n * STORAGE_LIMIT – 413 total storage quota exceeded\n * FILE_COUNT_LIMIT – 403 plan's maxFilesCount reached\n * MIME_TYPE_NOT_ALLOWED – 415 file type blocked by plan\n * COMPRESSION_NOT_ALLOWED – 403 compression level not permitted by plan\n * SUBSCRIPTION_INACTIVE – 402 user subscription is not active\n * FOLDER_COUNT_LIMIT – 403 plan's maxFoldersCount reached\n *\n * Auth errors\n * EMAIL_ALREADY_REGISTERED – 400 duplicate email on register\n * EMAIL_NOT_VERIFIED – 403 login attempted before verifying email\n * INVALID_CREDENTIALS – 400 wrong email or password\n * INVALID_OTP – 400 bad/expired verification code\n *\n * Generic\n * UNAUTHORIZED – 401\n * NOT_FOUND – 404\n * TIMEOUT – 408\n */\nexport const FluxsaveErrorCode = {\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n STORAGE_LIMIT: 'STORAGE_LIMIT',\n FILE_COUNT_LIMIT: 'FILE_COUNT_LIMIT',\n MIME_TYPE_NOT_ALLOWED: 'MIME_TYPE_NOT_ALLOWED',\n COMPRESSION_NOT_ALLOWED: 'COMPRESSION_NOT_ALLOWED',\n SUBSCRIPTION_INACTIVE: 'SUBSCRIPTION_INACTIVE',\n FOLDER_COUNT_LIMIT: 'FOLDER_COUNT_LIMIT',\n EMAIL_ALREADY_REGISTERED: 'EMAIL_ALREADY_REGISTERED',\n EMAIL_NOT_VERIFIED: 'EMAIL_NOT_VERIFIED',\n INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',\n INVALID_OTP: 'INVALID_OTP',\n UNAUTHORIZED: 'UNAUTHORIZED',\n NOT_FOUND: 'NOT_FOUND',\n TIMEOUT: 'TIMEOUT',\n UNKNOWN: 'UNKNOWN',\n} as const;\n\nexport type FluxsaveErrorCode = typeof FluxsaveErrorCode[keyof typeof FluxsaveErrorCode];\n\nfunction resolveErrorCode(status: number, message: string): FluxsaveErrorCode {\n const m = message.toLowerCase();\n if (status === 413 && m.includes('storage limit')) return FluxsaveErrorCode.STORAGE_LIMIT;\n if (status === 413) return FluxsaveErrorCode.FILE_TOO_LARGE;\n if (status === 415) return FluxsaveErrorCode.MIME_TYPE_NOT_ALLOWED;\n if (status === 402) return FluxsaveErrorCode.SUBSCRIPTION_INACTIVE;\n if (status === 403 && m.includes('compression')) return FluxsaveErrorCode.COMPRESSION_NOT_ALLOWED;\n if (status === 403 && m.includes('folder')) return FluxsaveErrorCode.FOLDER_COUNT_LIMIT;\n if (status === 403 && (m.includes('file') || m.includes('maximum'))) return FluxsaveErrorCode.FILE_COUNT_LIMIT;\n if (status === 403 && m.includes('email')) return FluxsaveErrorCode.EMAIL_NOT_VERIFIED;\n if (status === 400 && m.includes('already registered')) return FluxsaveErrorCode.EMAIL_ALREADY_REGISTERED;\n if (status === 400 && (m.includes('invalid email or password') || m.includes('invalid credentials'))) return FluxsaveErrorCode.INVALID_CREDENTIALS;\n if (status === 400 && m.includes('otp')) return FluxsaveErrorCode.INVALID_OTP;\n if (status === 401) return FluxsaveErrorCode.UNAUTHORIZED;\n if (status === 404) return FluxsaveErrorCode.NOT_FOUND;\n if (status === 408) return FluxsaveErrorCode.TIMEOUT;\n return FluxsaveErrorCode.UNKNOWN;\n}\n\nexport class FluxsaveError extends Error {\n status: number;\n code: FluxsaveErrorCode;\n data?: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = 'FluxsaveError';\n this.status = status;\n this.code = resolveErrorCode(status, message);\n this.data = data;\n }\n}\n\nexport class FluxsaveClient {\n private baseUrl: string;\n private apiKey?: string;\n private apiSecret?: string;\n private fetchImpl: FetchLike;\n private timeoutMs: number;\n private retry: Required<RetryOptions>;\n\n constructor(options: FluxsaveClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.apiSecret = options.apiSecret;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30000;\n this.retry = {\n retries: options.retry?.retries ?? 2,\n retryDelayMs: options.retry?.retryDelayMs ?? 400,\n retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504],\n };\n }\n\n setAuth(apiKey: string, apiSecret: string) {\n this.apiKey = apiKey;\n this.apiSecret = apiSecret;\n }\n\n setTimeout(timeoutMs: number) {\n this.timeoutMs = timeoutMs;\n }\n\n setRetry(options: RetryOptions) {\n this.retry = {\n retries: options.retries,\n retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,\n retryOn: options.retryOn ?? this.retry.retryOn,\n };\n }\n\n async uploadFile(file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n if (options.folderId) {\n form.append('folderId', options.folderId);\n }\n\n return this.request<ApiSuccess<FileRecord>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async uploadFiles(\n files: Array<{ file: Blob; filename?: string }>,\n options: UploadOptions = {}\n ): Promise<ApiSuccess<FileRecord[]>> {\n const form = new FormData();\n files.forEach((item) => {\n form.append('files', item.file, item.filename || 'file');\n });\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n if (options.folderId) {\n form.append('folderId', options.folderId);\n }\n\n return this.request<ApiSuccess<FileRecord[]>>('/api/v1/files/upload', {\n method: 'POST',\n body: form,\n });\n }\n\n async listFiles(folderId?: string): Promise<ApiSuccess<FileRecord[]>> {\n const path = folderId ? `/api/v1/files?folderId=${encodeURIComponent(folderId)}` : '/api/v1/files';\n return this.request<ApiSuccess<FileRecord[]>>(path, { method: 'GET' });\n }\n\n async listFolders(): Promise<ApiSuccess<Folder[]>> {\n return this.request<ApiSuccess<Folder[]>>('/api/v1/folders', { method: 'GET' });\n }\n\n async createFolder(name: string, parentId?: string): Promise<ApiSuccess<Folder>> {\n return this.request<ApiSuccess<Folder>>('/api/v1/folders', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, parentId }),\n });\n }\n\n async renameFolder(folderId: string, name: string): Promise<ApiSuccess<Folder>> {\n return this.request<ApiSuccess<Folder>>(`/api/v1/folders/${folderId}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n });\n }\n\n async deleteFolder(folderId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/folders/${folderId}`, { method: 'DELETE' });\n }\n\n async getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>> {\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/metadata/${fileId}`, {\n method: 'GET',\n });\n }\n\n async updateFile(fileId: string, file: Blob, options: UploadOptions = {}): Promise<ApiSuccess<FileRecord>> {\n const form = new FormData();\n form.append('file', file, options.filename || 'file');\n if (options.name) {\n form.append('name', options.name);\n }\n if (options.compression !== undefined) {\n form.append('compression', options.compression);\n }\n\n return this.request<ApiSuccess<FileRecord>>(`/api/v1/files/${fileId}`, {\n method: 'PUT',\n body: form,\n });\n }\n\n async deleteFile(fileId: string): Promise<ApiSuccess<null>> {\n return this.request<ApiSuccess<null>>(`/api/v1/files/${fileId}`, {\n method: 'DELETE',\n });\n }\n\n async getMetrics(): Promise<ApiSuccess<Metrics>> {\n return this.request<ApiSuccess<Metrics>>('/api/v1/metrics', {\n method: 'GET',\n });\n }\n\n buildFileUrl(fileId: string, options: TransformOptions = {}) {\n const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);\n if (options.width) url.searchParams.set('width', String(options.width));\n if (options.height) url.searchParams.set('height', String(options.height));\n if (options.format) url.searchParams.set('format', options.format);\n if (options.quality !== undefined) url.searchParams.set('quality', String(options.quality));\n return url.toString();\n }\n\n private async request<T>(path: string, init: RequestInit): Promise<T> {\n if (!this.apiKey || !this.apiSecret) {\n throw new FluxsaveError('API key and secret are required', 401);\n }\n\n const headers = new Headers(init.headers || {});\n headers.set('x-api-key', this.apiKey);\n headers.set('x-api-secret', this.apiSecret);\n\n let attempt = 0;\n while (true) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: controller.signal,\n });\n\n const contentType = response.headers.get('content-type') || '';\n const isJson = contentType.includes('application/json');\n const payload = isJson ? await response.json() : await response.text();\n\n if (!response.ok) {\n const message = (payload && (payload.message || payload.error)) || response.statusText;\n const error = new FluxsaveError(message, response.status, payload);\n if (this.shouldRetry(response.status, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n }\n\n return payload as T;\n } catch (error: any) {\n if (error?.name === 'AbortError') {\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw new FluxsaveError('Request timed out', 408);\n }\n\n if (this.shouldRetry(0, attempt)) {\n attempt += 1;\n await this.sleep(this.retry.retryDelayMs);\n continue;\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n }\n\n private shouldRetry(status: number, attempt: number) {\n if (attempt >= this.retry.retries) {\n return false;\n }\n if (status === 0) {\n return true;\n }\n return this.retry.retryOn.includes(status);\n }\n\n private async sleep(delayMs: number) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n}\n\nconst toArrayBuffer = (buffer: ArrayBuffer | Uint8Array) => {\n if (buffer instanceof Uint8Array) {\n const copy = new Uint8Array(buffer.byteLength);\n copy.set(buffer);\n return copy.buffer;\n }\n return buffer;\n};\n\nexport const fileFromBuffer = (\n buffer: ArrayBuffer | Uint8Array,\n _filename: string,\n type = 'application/octet-stream'\n) => {\n const data = toArrayBuffer(buffer);\n return new Blob([data], { type });\n};\n"],"mappings":"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","names":[]}
|