@fluxsave/sdk 0.1.3 → 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 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.transform !== void 0) {
70
- form.append("transform", String(options.transform));
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.transform !== void 0) {
86
- form.append("transform", String(options.transform));
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
- return this.request("/api/v1/files", {
95
- method: "GET"
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.transform !== void 0) {
110
- form.append("transform", String(options.transform));
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, filename, type = "application/octet-stream") => {
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
@@ -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
- transform?: boolean;
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, filename: string, type?: string) => Blob;
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
- transform?: boolean;
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, filename: string, type?: string) => Blob;
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.transform !== void 0) {
44
- form.append("transform", String(options.transform));
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.transform !== void 0) {
60
- form.append("transform", String(options.transform));
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
- return this.request("/api/v1/files", {
69
- method: "GET"
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.transform !== void 0) {
84
- form.append("transform", String(options.transform));
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, filename, type = "application/octet-stream") => {
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxsave/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "FluxSave SDK for API key uploads and file management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",