@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 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":[]}
@@ -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 };
@@ -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
+ }