@fluxmedia/s3 1.0.1 → 2.0.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
@@ -113,7 +113,9 @@ var S3Provider = class {
113
113
  secretAccessKey: this.config.secretAccessKey
114
114
  },
115
115
  ...this.config.endpoint && { endpoint: this.config.endpoint },
116
- ...this.config.forcePathStyle !== void 0 && { forcePathStyle: this.config.forcePathStyle }
116
+ ...this.config.forcePathStyle !== void 0 && {
117
+ forcePathStyle: this.config.forcePathStyle
118
+ }
117
119
  });
118
120
  this.client = client;
119
121
  return client;
@@ -123,7 +125,12 @@ var S3Provider = class {
123
125
  const Upload = await getUploadClass();
124
126
  try {
125
127
  const key = this.generateKey(options);
126
- const { contentType, extension } = await this.getContentType(file);
128
+ const isStream = this.isStreamInput(file);
129
+ const { contentType, extension } = isStream ? {
130
+ contentType: options?.contentType ?? "application/octet-stream",
131
+ extension: options?.contentType ? this.extensionFromMime(options.contentType) : ""
132
+ } : await this.getContentType(file);
133
+ const hasRetryConfig = !!options?.metadata?._retry;
127
134
  const upload = new Upload({
128
135
  client,
129
136
  params: {
@@ -141,20 +148,29 @@ var S3Provider = class {
141
148
  // Upload 4 parts in parallel
142
149
  partSize: 5 * 1024 * 1024,
143
150
  // 5MB per part (S3 minimum)
144
- leavePartsOnError: false
145
- // Auto-cleanup failed uploads
151
+ leavePartsOnError: hasRetryConfig,
152
+ // Keep parts for resume when retry is configured
153
+ ...options?.signal && { abortController: new AbortController() }
146
154
  });
147
- if (options?.onProgress) {
155
+ if (options?.onProgress || options?.onByteProgress) {
148
156
  upload.on("httpUploadProgress", (progress) => {
149
- if (progress.total) {
157
+ if (options?.onByteProgress) {
158
+ options.onByteProgress(progress.loaded ?? 0, progress.total);
159
+ }
160
+ if (options?.onProgress && progress.total) {
150
161
  const percentComplete = progress.loaded / progress.total * 100;
151
162
  options.onProgress(percentComplete);
152
163
  }
153
164
  });
154
165
  }
155
166
  await upload.done();
156
- const size = file instanceof Buffer ? file.byteLength : file.size;
157
- return this.createResult(key, size, extension, options?.metadata);
167
+ const size = file instanceof Buffer ? file.byteLength : typeof File !== "undefined" && file instanceof File ? file.size : 0;
168
+ return this.createResult(
169
+ key,
170
+ size,
171
+ extension,
172
+ options?.metadata
173
+ );
158
174
  } catch (error) {
159
175
  throw this.mapS3Error(error, core.MediaErrorCode.UPLOAD_FAILED);
160
176
  }
@@ -291,13 +307,49 @@ var S3Provider = class {
291
307
  async getContentType(file) {
292
308
  if (file instanceof Buffer) {
293
309
  const detected = await core.getFileType(file);
294
- return { contentType: detected?.mime ?? "application/octet-stream", extension: detected?.ext ?? "" };
310
+ return {
311
+ contentType: detected?.mime ?? "application/octet-stream",
312
+ extension: detected?.ext ?? ""
313
+ };
295
314
  }
296
- return { contentType: file.type || "application/octet-stream", extension: file.name.split(".").pop() || "" };
315
+ return {
316
+ contentType: file.type || "application/octet-stream",
317
+ extension: file.name.split(".").pop() || ""
318
+ };
319
+ }
320
+ /**
321
+ * Check whether the given input is a stream (Node.js Readable or Web ReadableStream).
322
+ */
323
+ isStreamInput(file) {
324
+ if (file instanceof Buffer) return false;
325
+ if (typeof File !== "undefined" && file instanceof File) return false;
326
+ return typeof file.pipe === "function" || typeof file.getReader === "function";
327
+ }
328
+ /**
329
+ * Derive a file extension from a MIME type string.
330
+ */
331
+ extensionFromMime(mime) {
332
+ const map = {
333
+ "image/jpeg": "jpg",
334
+ "image/png": "png",
335
+ "image/gif": "gif",
336
+ "image/webp": "webp",
337
+ "image/avif": "avif",
338
+ "image/svg+xml": "svg",
339
+ "video/mp4": "mp4",
340
+ "video/webm": "webm",
341
+ "video/quicktime": "mov",
342
+ "audio/mpeg": "mp3",
343
+ "audio/wav": "wav",
344
+ "application/pdf": "pdf",
345
+ "application/octet-stream": ""
346
+ };
347
+ return map[mime] ?? mime.split("/").pop() ?? "";
297
348
  }
298
349
  createResult(key, size, extension, metadata) {
299
350
  return {
300
351
  id: key,
352
+ storageKey: key,
301
353
  url: this.getUrl(key),
302
354
  publicUrl: this.getUrl(key),
303
355
  size,
package/dist/index.js CHANGED
@@ -111,7 +111,9 @@ var S3Provider = class {
111
111
  secretAccessKey: this.config.secretAccessKey
112
112
  },
113
113
  ...this.config.endpoint && { endpoint: this.config.endpoint },
114
- ...this.config.forcePathStyle !== void 0 && { forcePathStyle: this.config.forcePathStyle }
114
+ ...this.config.forcePathStyle !== void 0 && {
115
+ forcePathStyle: this.config.forcePathStyle
116
+ }
115
117
  });
116
118
  this.client = client;
117
119
  return client;
@@ -121,7 +123,12 @@ var S3Provider = class {
121
123
  const Upload = await getUploadClass();
122
124
  try {
123
125
  const key = this.generateKey(options);
124
- const { contentType, extension } = await this.getContentType(file);
126
+ const isStream = this.isStreamInput(file);
127
+ const { contentType, extension } = isStream ? {
128
+ contentType: options?.contentType ?? "application/octet-stream",
129
+ extension: options?.contentType ? this.extensionFromMime(options.contentType) : ""
130
+ } : await this.getContentType(file);
131
+ const hasRetryConfig = !!options?.metadata?._retry;
125
132
  const upload = new Upload({
126
133
  client,
127
134
  params: {
@@ -139,20 +146,29 @@ var S3Provider = class {
139
146
  // Upload 4 parts in parallel
140
147
  partSize: 5 * 1024 * 1024,
141
148
  // 5MB per part (S3 minimum)
142
- leavePartsOnError: false
143
- // Auto-cleanup failed uploads
149
+ leavePartsOnError: hasRetryConfig,
150
+ // Keep parts for resume when retry is configured
151
+ ...options?.signal && { abortController: new AbortController() }
144
152
  });
145
- if (options?.onProgress) {
153
+ if (options?.onProgress || options?.onByteProgress) {
146
154
  upload.on("httpUploadProgress", (progress) => {
147
- if (progress.total) {
155
+ if (options?.onByteProgress) {
156
+ options.onByteProgress(progress.loaded ?? 0, progress.total);
157
+ }
158
+ if (options?.onProgress && progress.total) {
148
159
  const percentComplete = progress.loaded / progress.total * 100;
149
160
  options.onProgress(percentComplete);
150
161
  }
151
162
  });
152
163
  }
153
164
  await upload.done();
154
- const size = file instanceof Buffer ? file.byteLength : file.size;
155
- return this.createResult(key, size, extension, options?.metadata);
165
+ const size = file instanceof Buffer ? file.byteLength : typeof File !== "undefined" && file instanceof File ? file.size : 0;
166
+ return this.createResult(
167
+ key,
168
+ size,
169
+ extension,
170
+ options?.metadata
171
+ );
156
172
  } catch (error) {
157
173
  throw this.mapS3Error(error, MediaErrorCode.UPLOAD_FAILED);
158
174
  }
@@ -289,13 +305,49 @@ var S3Provider = class {
289
305
  async getContentType(file) {
290
306
  if (file instanceof Buffer) {
291
307
  const detected = await getFileType(file);
292
- return { contentType: detected?.mime ?? "application/octet-stream", extension: detected?.ext ?? "" };
308
+ return {
309
+ contentType: detected?.mime ?? "application/octet-stream",
310
+ extension: detected?.ext ?? ""
311
+ };
293
312
  }
294
- return { contentType: file.type || "application/octet-stream", extension: file.name.split(".").pop() || "" };
313
+ return {
314
+ contentType: file.type || "application/octet-stream",
315
+ extension: file.name.split(".").pop() || ""
316
+ };
317
+ }
318
+ /**
319
+ * Check whether the given input is a stream (Node.js Readable or Web ReadableStream).
320
+ */
321
+ isStreamInput(file) {
322
+ if (file instanceof Buffer) return false;
323
+ if (typeof File !== "undefined" && file instanceof File) return false;
324
+ return typeof file.pipe === "function" || typeof file.getReader === "function";
325
+ }
326
+ /**
327
+ * Derive a file extension from a MIME type string.
328
+ */
329
+ extensionFromMime(mime) {
330
+ const map = {
331
+ "image/jpeg": "jpg",
332
+ "image/png": "png",
333
+ "image/gif": "gif",
334
+ "image/webp": "webp",
335
+ "image/avif": "avif",
336
+ "image/svg+xml": "svg",
337
+ "video/mp4": "mp4",
338
+ "video/webm": "webm",
339
+ "video/quicktime": "mov",
340
+ "audio/mpeg": "mp3",
341
+ "audio/wav": "wav",
342
+ "application/pdf": "pdf",
343
+ "application/octet-stream": ""
344
+ };
345
+ return map[mime] ?? mime.split("/").pop() ?? "";
295
346
  }
296
347
  createResult(key, size, extension, metadata) {
297
348
  return {
298
349
  id: key,
350
+ storageKey: key,
299
351
  url: this.getUrl(key),
300
352
  publicUrl: this.getUrl(key),
301
353
  size,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxmedia/s3",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "AWS S3 provider for FluxMedia - unified media upload library",
5
5
  "keywords": [
6
6
  "fluxmedia",
@@ -10,7 +10,7 @@
10
10
  "upload",
11
11
  "cloud"
12
12
  ],
13
- "homepage": "https://fluxmedia.dev",
13
+ "homepage": "https://www.fluxmedia.dev",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "https://github.com/codewithveek/fluxmedia.git",
@@ -41,7 +41,7 @@
41
41
  "!dist/**/*.map"
42
42
  ],
43
43
  "dependencies": {
44
- "@fluxmedia/core": "1.0.1"
44
+ "@fluxmedia/core": "2.0.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@aws-sdk/client-s3": "^3.980.0",