@fluxsave/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/index.cjs +217 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @fluxsave/sdk
|
|
2
|
+
|
|
3
|
+
JavaScript/TypeScript SDK for FluxSave. Supports API key + secret auth, file uploads, and file management.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @fluxsave/sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @fluxsave/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
https://fluxsave-sdk-docs.vercel.app/
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { FluxsaveClient } from '@fluxsave/sdk';
|
|
21
|
+
|
|
22
|
+
const client = new FluxsaveClient({
|
|
23
|
+
baseUrl: 'https://fluxsaveapi.lutheralien.com',
|
|
24
|
+
apiKey: 'fs_xxx',
|
|
25
|
+
apiSecret: 'sk_xxx',
|
|
26
|
+
timeoutMs: 30000,
|
|
27
|
+
retry: { retries: 2, retryDelayMs: 400 },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const file = new File([blobData], 'photo.png', { type: 'image/png' });
|
|
31
|
+
const uploaded = await client.uploadFile(file, { name: 'marketing-hero', transform: true });
|
|
32
|
+
|
|
33
|
+
const files = await client.listFiles();
|
|
34
|
+
await client.deleteFile(uploaded.data.fileId || uploaded.data._id);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Node example
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { FluxsaveClient } from '@fluxsave/sdk';
|
|
41
|
+
import { readFile } from 'node:fs/promises';
|
|
42
|
+
|
|
43
|
+
const client = new FluxsaveClient({
|
|
44
|
+
baseUrl: 'https://fluxsaveapi.lutheralien.com',
|
|
45
|
+
apiKey: process.env.FLUXSAVE_KEY!,
|
|
46
|
+
apiSecret: process.env.FLUXSAVE_SECRET!,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const buffer = await readFile('./photo.png');
|
|
50
|
+
const file = new Blob([buffer], { type: 'image/png' });
|
|
51
|
+
const response = await client.uploadFile(file, { filename: 'photo.png' });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
- `uploadFile(file, options)`
|
|
57
|
+
- `uploadFiles(files, options)`
|
|
58
|
+
- `listFiles()`
|
|
59
|
+
- `getFileMetadata(fileId)`
|
|
60
|
+
- `updateFile(fileId, file, options)`
|
|
61
|
+
- `deleteFile(fileId)`
|
|
62
|
+
- `getMetrics()`
|
|
63
|
+
- `buildFileUrl(fileId, options)`
|
|
64
|
+
- `setTimeout(timeoutMs)`
|
|
65
|
+
- `setRetry(options)`
|
|
66
|
+
- `fileFromBuffer(buffer, filename, type?)`
|
|
67
|
+
|
|
68
|
+
## Auth
|
|
69
|
+
|
|
70
|
+
Auth is sent as headers:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
X-API-KEY: <apiKey>
|
|
74
|
+
X-API-SECRET: <apiSecret>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
FluxsaveClient: () => FluxsaveClient,
|
|
24
|
+
FluxsaveError: () => FluxsaveError,
|
|
25
|
+
fileFromBuffer: () => fileFromBuffer
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var FluxsaveError = class extends Error {
|
|
29
|
+
constructor(message, status, data) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "FluxsaveError";
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.data = data;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var FluxsaveClient = class {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
39
|
+
this.apiKey = options.apiKey;
|
|
40
|
+
this.apiSecret = options.apiSecret;
|
|
41
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
42
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
43
|
+
this.retry = {
|
|
44
|
+
retries: options.retry?.retries ?? 2,
|
|
45
|
+
retryDelayMs: options.retry?.retryDelayMs ?? 400,
|
|
46
|
+
retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
setAuth(apiKey, apiSecret) {
|
|
50
|
+
this.apiKey = apiKey;
|
|
51
|
+
this.apiSecret = apiSecret;
|
|
52
|
+
}
|
|
53
|
+
setTimeout(timeoutMs) {
|
|
54
|
+
this.timeoutMs = timeoutMs;
|
|
55
|
+
}
|
|
56
|
+
setRetry(options) {
|
|
57
|
+
this.retry = {
|
|
58
|
+
retries: options.retries,
|
|
59
|
+
retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,
|
|
60
|
+
retryOn: options.retryOn ?? this.retry.retryOn
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async uploadFile(file, options = {}) {
|
|
64
|
+
const form = new FormData();
|
|
65
|
+
form.append("file", file, options.filename || "file");
|
|
66
|
+
if (options.name) {
|
|
67
|
+
form.append("name", options.name);
|
|
68
|
+
}
|
|
69
|
+
if (options.transform !== void 0) {
|
|
70
|
+
form.append("transform", String(options.transform));
|
|
71
|
+
}
|
|
72
|
+
return this.request("/api/v1/files/upload", {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: form
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async uploadFiles(files, options = {}) {
|
|
78
|
+
const form = new FormData();
|
|
79
|
+
files.forEach((item) => {
|
|
80
|
+
form.append("files", item.file, item.filename || "file");
|
|
81
|
+
});
|
|
82
|
+
if (options.name) {
|
|
83
|
+
form.append("name", options.name);
|
|
84
|
+
}
|
|
85
|
+
if (options.transform !== void 0) {
|
|
86
|
+
form.append("transform", String(options.transform));
|
|
87
|
+
}
|
|
88
|
+
return this.request("/api/v1/files/upload", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: form
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async listFiles() {
|
|
94
|
+
return this.request("/api/v1/files", {
|
|
95
|
+
method: "GET"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async getFileMetadata(fileId) {
|
|
99
|
+
return this.request(`/api/v1/files/metadata/${fileId}`, {
|
|
100
|
+
method: "GET"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async updateFile(fileId, file, options = {}) {
|
|
104
|
+
const form = new FormData();
|
|
105
|
+
form.append("file", file, options.filename || "file");
|
|
106
|
+
if (options.name) {
|
|
107
|
+
form.append("name", options.name);
|
|
108
|
+
}
|
|
109
|
+
if (options.transform !== void 0) {
|
|
110
|
+
form.append("transform", String(options.transform));
|
|
111
|
+
}
|
|
112
|
+
return this.request(`/api/v1/files/${fileId}`, {
|
|
113
|
+
method: "PUT",
|
|
114
|
+
body: form
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async deleteFile(fileId) {
|
|
118
|
+
return this.request(`/api/v1/files/${fileId}`, {
|
|
119
|
+
method: "DELETE"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async getMetrics() {
|
|
123
|
+
return this.request("/api/v1/metrics", {
|
|
124
|
+
method: "GET"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
buildFileUrl(fileId, options = {}) {
|
|
128
|
+
const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);
|
|
129
|
+
if (options.width) url.searchParams.set("width", String(options.width));
|
|
130
|
+
if (options.height) url.searchParams.set("height", String(options.height));
|
|
131
|
+
if (options.format) url.searchParams.set("format", options.format);
|
|
132
|
+
if (options.quality !== void 0) url.searchParams.set("quality", String(options.quality));
|
|
133
|
+
return url.toString();
|
|
134
|
+
}
|
|
135
|
+
async request(path, init) {
|
|
136
|
+
if (!this.apiKey || !this.apiSecret) {
|
|
137
|
+
throw new FluxsaveError("API key and secret are required", 401);
|
|
138
|
+
}
|
|
139
|
+
const headers = new Headers(init.headers || {});
|
|
140
|
+
headers.set("x-api-key", this.apiKey);
|
|
141
|
+
headers.set("x-api-secret", this.apiSecret);
|
|
142
|
+
let attempt = 0;
|
|
143
|
+
while (true) {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
146
|
+
try {
|
|
147
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
148
|
+
...init,
|
|
149
|
+
headers,
|
|
150
|
+
signal: controller.signal
|
|
151
|
+
});
|
|
152
|
+
const contentType = response.headers.get("content-type") || "";
|
|
153
|
+
const isJson = contentType.includes("application/json");
|
|
154
|
+
const payload = isJson ? await response.json() : await response.text();
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
const message = payload && (payload.message || payload.error) || response.statusText;
|
|
157
|
+
const error = new FluxsaveError(message, response.status, payload);
|
|
158
|
+
if (this.shouldRetry(response.status, attempt)) {
|
|
159
|
+
attempt += 1;
|
|
160
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
return payload;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error?.name === "AbortError") {
|
|
168
|
+
if (this.shouldRetry(0, attempt)) {
|
|
169
|
+
attempt += 1;
|
|
170
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
throw new FluxsaveError("Request timed out", 408);
|
|
174
|
+
}
|
|
175
|
+
if (this.shouldRetry(0, attempt)) {
|
|
176
|
+
attempt += 1;
|
|
177
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw error;
|
|
181
|
+
} finally {
|
|
182
|
+
clearTimeout(timeoutId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
shouldRetry(status, attempt) {
|
|
187
|
+
if (attempt >= this.retry.retries) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (status === 0) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return this.retry.retryOn.includes(status);
|
|
194
|
+
}
|
|
195
|
+
async sleep(delayMs) {
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var toArrayBuffer = (buffer) => {
|
|
200
|
+
if (buffer instanceof Uint8Array) {
|
|
201
|
+
const copy = new Uint8Array(buffer.byteLength);
|
|
202
|
+
copy.set(buffer);
|
|
203
|
+
return copy.buffer;
|
|
204
|
+
}
|
|
205
|
+
return buffer;
|
|
206
|
+
};
|
|
207
|
+
var fileFromBuffer = (buffer, filename, type = "application/octet-stream") => {
|
|
208
|
+
const data = toArrayBuffer(buffer);
|
|
209
|
+
return new Blob([data], { type });
|
|
210
|
+
};
|
|
211
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
212
|
+
0 && (module.exports = {
|
|
213
|
+
FluxsaveClient,
|
|
214
|
+
FluxsaveError,
|
|
215
|
+
fileFromBuffer
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
type ApiSuccess<T> = {
|
|
2
|
+
status: number;
|
|
3
|
+
message: string;
|
|
4
|
+
data: T;
|
|
5
|
+
};
|
|
6
|
+
type ApiError = {
|
|
7
|
+
status: number;
|
|
8
|
+
message: string;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
};
|
|
11
|
+
type FileRecord = {
|
|
12
|
+
_id?: string;
|
|
13
|
+
fileId?: string;
|
|
14
|
+
cloudId?: string;
|
|
15
|
+
filename: string;
|
|
16
|
+
originalFilename?: string;
|
|
17
|
+
url: string;
|
|
18
|
+
size: number;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
createdAt?: string;
|
|
23
|
+
updatedAt?: string;
|
|
24
|
+
};
|
|
25
|
+
type Metrics = {
|
|
26
|
+
totalFiles: number;
|
|
27
|
+
totalStorageBytes: number;
|
|
28
|
+
averageFileSize: number;
|
|
29
|
+
storageLimitBytes: number;
|
|
30
|
+
storageRemainingBytes: number;
|
|
31
|
+
storageUsedPercent: number;
|
|
32
|
+
uploadsLast7Days: number;
|
|
33
|
+
latestUploadAt?: string | null;
|
|
34
|
+
byMimeType: Record<string, number>;
|
|
35
|
+
};
|
|
36
|
+
type FetchLike = typeof fetch;
|
|
37
|
+
type RetryOptions = {
|
|
38
|
+
retries: number;
|
|
39
|
+
retryDelayMs?: number;
|
|
40
|
+
retryOn?: number[];
|
|
41
|
+
};
|
|
42
|
+
type FluxsaveClientOptions = {
|
|
43
|
+
baseUrl: string;
|
|
44
|
+
apiKey?: string;
|
|
45
|
+
apiSecret?: string;
|
|
46
|
+
fetchImpl?: FetchLike;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
retry?: RetryOptions;
|
|
49
|
+
};
|
|
50
|
+
type UploadOptions = {
|
|
51
|
+
name?: string;
|
|
52
|
+
transform?: boolean;
|
|
53
|
+
filename?: string;
|
|
54
|
+
};
|
|
55
|
+
type TransformOptions = {
|
|
56
|
+
width?: number;
|
|
57
|
+
height?: number;
|
|
58
|
+
format?: string;
|
|
59
|
+
quality?: number | string;
|
|
60
|
+
};
|
|
61
|
+
declare class FluxsaveError extends Error {
|
|
62
|
+
status: number;
|
|
63
|
+
data?: unknown;
|
|
64
|
+
constructor(message: string, status: number, data?: unknown);
|
|
65
|
+
}
|
|
66
|
+
declare class FluxsaveClient {
|
|
67
|
+
private baseUrl;
|
|
68
|
+
private apiKey?;
|
|
69
|
+
private apiSecret?;
|
|
70
|
+
private fetchImpl;
|
|
71
|
+
private timeoutMs;
|
|
72
|
+
private retry;
|
|
73
|
+
constructor(options: FluxsaveClientOptions);
|
|
74
|
+
setAuth(apiKey: string, apiSecret: string): void;
|
|
75
|
+
setTimeout(timeoutMs: number): void;
|
|
76
|
+
setRetry(options: RetryOptions): void;
|
|
77
|
+
uploadFile(file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
78
|
+
uploadFiles(files: Array<{
|
|
79
|
+
file: Blob;
|
|
80
|
+
filename?: string;
|
|
81
|
+
}>, options?: UploadOptions): Promise<ApiSuccess<FileRecord[]>>;
|
|
82
|
+
listFiles(): Promise<ApiSuccess<FileRecord[]>>;
|
|
83
|
+
getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>>;
|
|
84
|
+
updateFile(fileId: string, file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
85
|
+
deleteFile(fileId: string): Promise<ApiSuccess<null>>;
|
|
86
|
+
getMetrics(): Promise<ApiSuccess<Metrics>>;
|
|
87
|
+
buildFileUrl(fileId: string, options?: TransformOptions): string;
|
|
88
|
+
private request;
|
|
89
|
+
private shouldRetry;
|
|
90
|
+
private sleep;
|
|
91
|
+
}
|
|
92
|
+
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array, filename: string, type?: string) => Blob;
|
|
93
|
+
|
|
94
|
+
export { type ApiError, type ApiSuccess, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
type ApiSuccess<T> = {
|
|
2
|
+
status: number;
|
|
3
|
+
message: string;
|
|
4
|
+
data: T;
|
|
5
|
+
};
|
|
6
|
+
type ApiError = {
|
|
7
|
+
status: number;
|
|
8
|
+
message: string;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
};
|
|
11
|
+
type FileRecord = {
|
|
12
|
+
_id?: string;
|
|
13
|
+
fileId?: string;
|
|
14
|
+
cloudId?: string;
|
|
15
|
+
filename: string;
|
|
16
|
+
originalFilename?: string;
|
|
17
|
+
url: string;
|
|
18
|
+
size: number;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
createdAt?: string;
|
|
23
|
+
updatedAt?: string;
|
|
24
|
+
};
|
|
25
|
+
type Metrics = {
|
|
26
|
+
totalFiles: number;
|
|
27
|
+
totalStorageBytes: number;
|
|
28
|
+
averageFileSize: number;
|
|
29
|
+
storageLimitBytes: number;
|
|
30
|
+
storageRemainingBytes: number;
|
|
31
|
+
storageUsedPercent: number;
|
|
32
|
+
uploadsLast7Days: number;
|
|
33
|
+
latestUploadAt?: string | null;
|
|
34
|
+
byMimeType: Record<string, number>;
|
|
35
|
+
};
|
|
36
|
+
type FetchLike = typeof fetch;
|
|
37
|
+
type RetryOptions = {
|
|
38
|
+
retries: number;
|
|
39
|
+
retryDelayMs?: number;
|
|
40
|
+
retryOn?: number[];
|
|
41
|
+
};
|
|
42
|
+
type FluxsaveClientOptions = {
|
|
43
|
+
baseUrl: string;
|
|
44
|
+
apiKey?: string;
|
|
45
|
+
apiSecret?: string;
|
|
46
|
+
fetchImpl?: FetchLike;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
retry?: RetryOptions;
|
|
49
|
+
};
|
|
50
|
+
type UploadOptions = {
|
|
51
|
+
name?: string;
|
|
52
|
+
transform?: boolean;
|
|
53
|
+
filename?: string;
|
|
54
|
+
};
|
|
55
|
+
type TransformOptions = {
|
|
56
|
+
width?: number;
|
|
57
|
+
height?: number;
|
|
58
|
+
format?: string;
|
|
59
|
+
quality?: number | string;
|
|
60
|
+
};
|
|
61
|
+
declare class FluxsaveError extends Error {
|
|
62
|
+
status: number;
|
|
63
|
+
data?: unknown;
|
|
64
|
+
constructor(message: string, status: number, data?: unknown);
|
|
65
|
+
}
|
|
66
|
+
declare class FluxsaveClient {
|
|
67
|
+
private baseUrl;
|
|
68
|
+
private apiKey?;
|
|
69
|
+
private apiSecret?;
|
|
70
|
+
private fetchImpl;
|
|
71
|
+
private timeoutMs;
|
|
72
|
+
private retry;
|
|
73
|
+
constructor(options: FluxsaveClientOptions);
|
|
74
|
+
setAuth(apiKey: string, apiSecret: string): void;
|
|
75
|
+
setTimeout(timeoutMs: number): void;
|
|
76
|
+
setRetry(options: RetryOptions): void;
|
|
77
|
+
uploadFile(file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
78
|
+
uploadFiles(files: Array<{
|
|
79
|
+
file: Blob;
|
|
80
|
+
filename?: string;
|
|
81
|
+
}>, options?: UploadOptions): Promise<ApiSuccess<FileRecord[]>>;
|
|
82
|
+
listFiles(): Promise<ApiSuccess<FileRecord[]>>;
|
|
83
|
+
getFileMetadata(fileId: string): Promise<ApiSuccess<FileRecord>>;
|
|
84
|
+
updateFile(fileId: string, file: Blob, options?: UploadOptions): Promise<ApiSuccess<FileRecord>>;
|
|
85
|
+
deleteFile(fileId: string): Promise<ApiSuccess<null>>;
|
|
86
|
+
getMetrics(): Promise<ApiSuccess<Metrics>>;
|
|
87
|
+
buildFileUrl(fileId: string, options?: TransformOptions): string;
|
|
88
|
+
private request;
|
|
89
|
+
private shouldRetry;
|
|
90
|
+
private sleep;
|
|
91
|
+
}
|
|
92
|
+
declare const fileFromBuffer: (buffer: ArrayBuffer | Uint8Array, filename: string, type?: string) => Blob;
|
|
93
|
+
|
|
94
|
+
export { type ApiError, type ApiSuccess, type FetchLike, type FileRecord, FluxsaveClient, type FluxsaveClientOptions, FluxsaveError, type Metrics, type RetryOptions, type TransformOptions, type UploadOptions, fileFromBuffer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var FluxsaveError = class extends Error {
|
|
3
|
+
constructor(message, status, data) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "FluxsaveError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.data = data;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var FluxsaveClient = class {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
13
|
+
this.apiKey = options.apiKey;
|
|
14
|
+
this.apiSecret = options.apiSecret;
|
|
15
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
16
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
17
|
+
this.retry = {
|
|
18
|
+
retries: options.retry?.retries ?? 2,
|
|
19
|
+
retryDelayMs: options.retry?.retryDelayMs ?? 400,
|
|
20
|
+
retryOn: options.retry?.retryOn ?? [429, 500, 502, 503, 504]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
setAuth(apiKey, apiSecret) {
|
|
24
|
+
this.apiKey = apiKey;
|
|
25
|
+
this.apiSecret = apiSecret;
|
|
26
|
+
}
|
|
27
|
+
setTimeout(timeoutMs) {
|
|
28
|
+
this.timeoutMs = timeoutMs;
|
|
29
|
+
}
|
|
30
|
+
setRetry(options) {
|
|
31
|
+
this.retry = {
|
|
32
|
+
retries: options.retries,
|
|
33
|
+
retryDelayMs: options.retryDelayMs ?? this.retry.retryDelayMs,
|
|
34
|
+
retryOn: options.retryOn ?? this.retry.retryOn
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async uploadFile(file, options = {}) {
|
|
38
|
+
const form = new FormData();
|
|
39
|
+
form.append("file", file, options.filename || "file");
|
|
40
|
+
if (options.name) {
|
|
41
|
+
form.append("name", options.name);
|
|
42
|
+
}
|
|
43
|
+
if (options.transform !== void 0) {
|
|
44
|
+
form.append("transform", String(options.transform));
|
|
45
|
+
}
|
|
46
|
+
return this.request("/api/v1/files/upload", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: form
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async uploadFiles(files, options = {}) {
|
|
52
|
+
const form = new FormData();
|
|
53
|
+
files.forEach((item) => {
|
|
54
|
+
form.append("files", item.file, item.filename || "file");
|
|
55
|
+
});
|
|
56
|
+
if (options.name) {
|
|
57
|
+
form.append("name", options.name);
|
|
58
|
+
}
|
|
59
|
+
if (options.transform !== void 0) {
|
|
60
|
+
form.append("transform", String(options.transform));
|
|
61
|
+
}
|
|
62
|
+
return this.request("/api/v1/files/upload", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: form
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async listFiles() {
|
|
68
|
+
return this.request("/api/v1/files", {
|
|
69
|
+
method: "GET"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async getFileMetadata(fileId) {
|
|
73
|
+
return this.request(`/api/v1/files/metadata/${fileId}`, {
|
|
74
|
+
method: "GET"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async updateFile(fileId, file, options = {}) {
|
|
78
|
+
const form = new FormData();
|
|
79
|
+
form.append("file", file, options.filename || "file");
|
|
80
|
+
if (options.name) {
|
|
81
|
+
form.append("name", options.name);
|
|
82
|
+
}
|
|
83
|
+
if (options.transform !== void 0) {
|
|
84
|
+
form.append("transform", String(options.transform));
|
|
85
|
+
}
|
|
86
|
+
return this.request(`/api/v1/files/${fileId}`, {
|
|
87
|
+
method: "PUT",
|
|
88
|
+
body: form
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async deleteFile(fileId) {
|
|
92
|
+
return this.request(`/api/v1/files/${fileId}`, {
|
|
93
|
+
method: "DELETE"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async getMetrics() {
|
|
97
|
+
return this.request("/api/v1/metrics", {
|
|
98
|
+
method: "GET"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
buildFileUrl(fileId, options = {}) {
|
|
102
|
+
const url = new URL(`${this.baseUrl}/api/v1/files/${fileId}`);
|
|
103
|
+
if (options.width) url.searchParams.set("width", String(options.width));
|
|
104
|
+
if (options.height) url.searchParams.set("height", String(options.height));
|
|
105
|
+
if (options.format) url.searchParams.set("format", options.format);
|
|
106
|
+
if (options.quality !== void 0) url.searchParams.set("quality", String(options.quality));
|
|
107
|
+
return url.toString();
|
|
108
|
+
}
|
|
109
|
+
async request(path, init) {
|
|
110
|
+
if (!this.apiKey || !this.apiSecret) {
|
|
111
|
+
throw new FluxsaveError("API key and secret are required", 401);
|
|
112
|
+
}
|
|
113
|
+
const headers = new Headers(init.headers || {});
|
|
114
|
+
headers.set("x-api-key", this.apiKey);
|
|
115
|
+
headers.set("x-api-secret", this.apiSecret);
|
|
116
|
+
let attempt = 0;
|
|
117
|
+
while (true) {
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
120
|
+
try {
|
|
121
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
122
|
+
...init,
|
|
123
|
+
headers,
|
|
124
|
+
signal: controller.signal
|
|
125
|
+
});
|
|
126
|
+
const contentType = response.headers.get("content-type") || "";
|
|
127
|
+
const isJson = contentType.includes("application/json");
|
|
128
|
+
const payload = isJson ? await response.json() : await response.text();
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const message = payload && (payload.message || payload.error) || response.statusText;
|
|
131
|
+
const error = new FluxsaveError(message, response.status, payload);
|
|
132
|
+
if (this.shouldRetry(response.status, attempt)) {
|
|
133
|
+
attempt += 1;
|
|
134
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
return payload;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error?.name === "AbortError") {
|
|
142
|
+
if (this.shouldRetry(0, attempt)) {
|
|
143
|
+
attempt += 1;
|
|
144
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
throw new FluxsaveError("Request timed out", 408);
|
|
148
|
+
}
|
|
149
|
+
if (this.shouldRetry(0, attempt)) {
|
|
150
|
+
attempt += 1;
|
|
151
|
+
await this.sleep(this.retry.retryDelayMs);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
} finally {
|
|
156
|
+
clearTimeout(timeoutId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
shouldRetry(status, attempt) {
|
|
161
|
+
if (attempt >= this.retry.retries) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (status === 0) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return this.retry.retryOn.includes(status);
|
|
168
|
+
}
|
|
169
|
+
async sleep(delayMs) {
|
|
170
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var toArrayBuffer = (buffer) => {
|
|
174
|
+
if (buffer instanceof Uint8Array) {
|
|
175
|
+
const copy = new Uint8Array(buffer.byteLength);
|
|
176
|
+
copy.set(buffer);
|
|
177
|
+
return copy.buffer;
|
|
178
|
+
}
|
|
179
|
+
return buffer;
|
|
180
|
+
};
|
|
181
|
+
var fileFromBuffer = (buffer, filename, type = "application/octet-stream") => {
|
|
182
|
+
const data = toArrayBuffer(buffer);
|
|
183
|
+
return new Blob([data], { type });
|
|
184
|
+
};
|
|
185
|
+
export {
|
|
186
|
+
FluxsaveClient,
|
|
187
|
+
FluxsaveError,
|
|
188
|
+
fileFromBuffer
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluxsave/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "FluxSave SDK for API key uploads and file management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"fluxsave",
|
|
26
|
+
"sdk",
|
|
27
|
+
"storage",
|
|
28
|
+
"upload"
|
|
29
|
+
],
|
|
30
|
+
"author": "FluxSave",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"tsup": "^8.1.0",
|
|
34
|
+
"typescript": "^5.9.2"
|
|
35
|
+
}
|
|
36
|
+
}
|