nuxt-ui-elements 0.1.31 → 0.1.33

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.
Files changed (22) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/composables/useUploadManager/index.d.ts +4 -4
  3. package/dist/runtime/composables/useUploadManager/index.js +132 -87
  4. package/dist/runtime/composables/useUploadManager/plugins/image-compressor.d.ts +1 -1
  5. package/dist/runtime/composables/useUploadManager/plugins/image-compressor.js +2 -2
  6. package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.d.ts +12 -1
  7. package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.js +62 -32
  8. package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.d.ts +4 -3
  9. package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.js +87 -56
  10. package/dist/runtime/composables/useUploadManager/plugins/video-compressor.d.ts +1 -1
  11. package/dist/runtime/composables/useUploadManager/plugins/video-compressor.js +17 -7
  12. package/dist/runtime/composables/useUploadManager/types.d.ts +213 -9
  13. package/dist/runtime/composables/useUploadManager/types.js +6 -0
  14. package/dist/runtime/composables/useUploadManager/utils.d.ts +23 -0
  15. package/dist/runtime/composables/useUploadManager/utils.js +45 -0
  16. package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.d.ts +1 -1
  17. package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.js +7 -3
  18. package/dist/runtime/composables/useUploadManager/validators/max-file-size.d.ts +2 -2
  19. package/dist/runtime/composables/useUploadManager/validators/max-file-size.js +7 -3
  20. package/dist/runtime/composables/useUploadManager/validators/max-files.d.ts +1 -1
  21. package/dist/runtime/composables/useUploadManager/validators/max-files.js +7 -3
  22. package/package.json +6 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ui-elements",
3
3
  "configKey": "uiElements",
4
- "version": "0.1.31",
4
+ "version": "0.1.33",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -184,8 +184,8 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
184
184
  };
185
185
  })[]>>;
186
186
  totalProgress: import("vue").ComputedRef<number>;
187
- addFiles: (newFiles: File[]) => Promise<(UploadFile | null)[]>;
188
- addFile: (file: File) => Promise<UploadFile | null>;
187
+ addFiles: (newFiles: File[]) => Promise<UploadFile[]>;
188
+ addFile: (file: File) => Promise<UploadFile>;
189
189
  onGetRemoteFile: (fn: GetRemoteFileFn) => void;
190
190
  onUpload: (fn: UploadFn<TUploadResult>) => void;
191
191
  removeFile: (fileId: string) => Promise<void>;
@@ -453,14 +453,14 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
453
453
  } | undefined;
454
454
  uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
455
455
  meta: Record<string, unknown>;
456
- } | undefined;
456
+ };
457
457
  upload: () => Promise<void>;
458
458
  reset: () => void;
459
459
  status: import("vue").Ref<UploadStatus, UploadStatus>;
460
460
  getFileData: (fileId: string) => Promise<Blob>;
461
461
  getFileURL: (fileId: string) => Promise<string>;
462
462
  getFileStream: (fileId: string) => Promise<ReadableStream<Uint8Array>>;
463
- replaceFileData: (fileId: string, newData: Blob, newName?: string) => Promise<void>;
463
+ replaceFileData: (fileId: string, newData: Blob, newName?: string, shouldAutoUpload?: boolean) => Promise<UploadFile>;
464
464
  updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
465
465
  initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
466
466
  addPlugin: (plugin: UploaderPlugin<any, any>) => void;
@@ -1,7 +1,11 @@
1
1
  import mitt from "mitt";
2
- import { computed, readonly, ref } from "vue";
3
- import { ValidatorAllowedFileTypes, ValidatorMaxfileSize, ValidatorMaxFiles } from "./validators/index.js";
2
+ import { computed, onBeforeUnmount, readonly, ref } from "vue";
3
+ import { ValidatorAllowedFileTypes, ValidatorMaxFileSize, ValidatorMaxFiles } from "./validators/index.js";
4
4
  import { PluginThumbnailGenerator, PluginImageCompressor } from "./plugins/index.js";
5
+ import { createPluginContext, createFileError, cleanupObjectURLs } from "./utils.js";
6
+ function isStoragePlugin(plugin) {
7
+ return plugin !== null && "upload" in plugin.hooks;
8
+ }
5
9
  function getExtension(fullFileName) {
6
10
  const lastDot = fullFileName.lastIndexOf(".");
7
11
  if (lastDot === -1 || lastDot === fullFileName.length - 1) {
@@ -10,6 +14,7 @@ function getExtension(fullFileName) {
10
14
  return fullFileName.slice(lastDot + 1).toLocaleLowerCase();
11
15
  }
12
16
  const defaultOptions = {
17
+ storage: void 0,
13
18
  plugins: [],
14
19
  maxFileSize: false,
15
20
  allowedFileTypes: false,
@@ -23,6 +28,7 @@ export const useUploadManager = (_options = {}) => {
23
28
  const files = ref([]);
24
29
  const emitter = mitt();
25
30
  const status = ref("waiting");
31
+ const createdObjectURLs = /* @__PURE__ */ new Map();
26
32
  const pluginEmitFunctions = /* @__PURE__ */ new Map();
27
33
  const getPluginEmitFn = (pluginId) => {
28
34
  let emitFn = pluginEmitFunctions.get(pluginId);
@@ -46,45 +52,53 @@ export const useUploadManager = (_options = {}) => {
46
52
  const sum = files.value.reduce((acc, file) => acc + file.progress.percentage, 0);
47
53
  return Math.round(sum / files.value.length);
48
54
  });
49
- const isStoragePlugin = (plugin) => {
50
- return !!(plugin.hooks.upload || plugin.hooks.getRemoteFile || plugin.hooks.remove);
51
- };
52
55
  const getStoragePlugin = () => {
56
+ if (options.storage) {
57
+ return options.storage;
58
+ }
53
59
  if (!options.plugins) return null;
54
60
  for (let i = options.plugins.length - 1; i >= 0; i--) {
55
- const plugin = options.plugins[i];
56
- if (plugin && isStoragePlugin(plugin)) {
61
+ const plugin = options.plugins[i] || null;
62
+ if (isStoragePlugin(plugin)) {
63
+ if (import.meta.dev) {
64
+ console.warn(
65
+ `[useUploadManager] Storage plugin "${plugin.id}" found in plugins array.
66
+ This is deprecated. Use the 'storage' option instead:
67
+
68
+ useUploadManager({ storage: ${plugin.id}(...) })`
69
+ );
70
+ }
57
71
  return plugin;
58
72
  }
59
73
  }
60
74
  return null;
61
75
  };
62
76
  const addPlugin = (plugin) => {
63
- if (isStoragePlugin(plugin)) {
64
- const existingStorage = options.plugins?.find((p) => isStoragePlugin(p));
65
- if (existingStorage && import.meta.dev) {
77
+ const hasUploadHook = "upload" in plugin.hooks;
78
+ if (hasUploadHook) {
79
+ if (import.meta.dev) {
66
80
  console.warn(
67
- `[useUploadManager] Multiple storage plugins detected!
68
-
69
- You're trying to add "${plugin.id}" but "${existingStorage.id}" is already registered.
70
- The LAST storage plugin ("${plugin.id}") will be used for uploads.
81
+ `[useUploadManager] Storage plugin "${plugin.id}" should use the 'storage' option instead of 'plugins':
71
82
 
72
- \u{1F4A1} If you need multiple storage destinations, create separate uploader instances:
73
-
74
- const s3Uploader = useUploadManager({ plugins: [PluginS3Storage({ ... })] })
75
- const azureUploader = useUploadManager({ plugins: [PluginAzureStorage({ ... })] })
83
+ useUploadManager({
84
+ storage: ${plugin.id}({ ... }), // \u2713 Correct
85
+ plugins: [...] // Only for validators/processors
86
+ })
76
87
  `
77
88
  );
78
89
  }
79
90
  }
80
- ;
81
- options.plugins?.push(plugin);
91
+ if (options.plugins) {
92
+ options.plugins.push(plugin);
93
+ } else {
94
+ options.plugins = [plugin];
95
+ }
82
96
  };
83
97
  if (options.maxFiles !== false && options.maxFiles !== void 0) {
84
98
  addPlugin(ValidatorMaxFiles({ maxFiles: options.maxFiles }));
85
99
  }
86
100
  if (options.maxFileSize !== false && options.maxFileSize !== void 0) {
87
- addPlugin(ValidatorMaxfileSize({ maxFileSize: options.maxFileSize }));
101
+ addPlugin(ValidatorMaxFileSize({ maxFileSize: options.maxFileSize }));
88
102
  }
89
103
  if (options.allowedFileTypes !== false && options.allowedFileTypes !== void 0 && options.allowedFileTypes.length > 0) {
90
104
  addPlugin(ValidatorAllowedFileTypes({ allowedFileTypes: options.allowedFileTypes }));
@@ -93,8 +107,8 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
93
107
  const thumbOpts = options.thumbnails === true ? {} : options.thumbnails || {};
94
108
  addPlugin(
95
109
  PluginThumbnailGenerator({
96
- width: thumbOpts.width ?? 128,
97
- height: thumbOpts.height ?? 128,
110
+ maxWidth: thumbOpts.width ?? 128,
111
+ maxHeight: thumbOpts.height ?? 128,
98
112
  quality: thumbOpts.quality ?? 1
99
113
  })
100
114
  );
@@ -131,14 +145,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
131
145
  const storagePlugin = getStoragePlugin();
132
146
  let remoteFileData;
133
147
  if (storagePlugin?.hooks.getRemoteFile) {
134
- const context = {
135
- files: files.value,
136
- options,
137
- emit: (event, payload) => {
138
- const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
139
- emitter.emit(prefixedEvent, payload);
140
- }
141
- };
148
+ const context = createPluginContext(storagePlugin.id, files.value, options, emitter);
142
149
  remoteFileData = await storagePlugin.hooks.getRemoteFile(file.id, context);
143
150
  } else {
144
151
  remoteFileData = await getRemoteFileFn(file.id);
@@ -154,12 +161,12 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
154
161
  size: remoteFileData.size,
155
162
  mimeType: remoteFileData.mimeType,
156
163
  remoteUrl: remoteFileData.remoteUrl,
164
+ preview: remoteFileData.preview || file.preview || remoteFileData.remoteUrl,
165
+ // Use preview from storage, passed-in value, or fallback to remoteUrl
157
166
  source: "storage"
158
167
  // File loaded from remote storage
159
168
  };
160
- const processedFile = await runPluginStage("process", existingFile);
161
- if (!processedFile) return null;
162
- return processedFile;
169
+ return existingFile;
163
170
  })
164
171
  );
165
172
  const filteredFiles = initializedfiles.filter((f) => f !== null);
@@ -183,46 +190,55 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
183
190
  extension
184
191
  }
185
192
  };
186
- const validatedFile = await runPluginStage("validate", uploadFile);
187
- if (!validatedFile) return null;
188
- const processedFile = await runPluginStage("process", validatedFile);
189
- if (!processedFile) return null;
190
- files.value.push(processedFile);
191
- emitter.emit("file:added", processedFile);
192
- if (options.autoProceed) {
193
- upload();
193
+ try {
194
+ const validatedFile = await runPluginStage("validate", uploadFile);
195
+ if (!validatedFile) {
196
+ throw new Error(`File validation failed for ${file.name}`);
197
+ }
198
+ const preprocessedFile = await runPluginStage("preprocess", validatedFile);
199
+ const fileToAdd = preprocessedFile || validatedFile;
200
+ files.value.push(fileToAdd);
201
+ emitter.emit("file:added", fileToAdd);
202
+ if (options.autoProceed) {
203
+ upload();
204
+ }
205
+ return validatedFile;
206
+ } catch (err) {
207
+ const error = createFileError(uploadFile, err);
208
+ const fileWithError = { ...uploadFile, status: "error", error };
209
+ files.value.push(fileWithError);
210
+ emitter.emit("file:error", { file: fileWithError, error });
211
+ throw err;
194
212
  }
195
- return processedFile;
196
213
  };
197
- const addFiles = (newFiles) => {
198
- return Promise.all(newFiles.map((file) => addFile(file)));
214
+ const addFiles = async (newFiles) => {
215
+ const results = await Promise.allSettled(newFiles.map((file) => addFile(file)));
216
+ const addedFiles = results.filter((r) => r.status === "fulfilled").map((r) => r.value);
217
+ return addedFiles;
199
218
  };
200
219
  const removeFile = async (fileId) => {
201
- const file = getFile(fileId);
220
+ const file = files.value.find((f) => f.id === fileId);
202
221
  if (!file) return;
203
222
  if (file.remoteUrl) {
204
223
  const storagePlugin = getStoragePlugin();
205
224
  if (storagePlugin?.hooks.remove) {
206
225
  try {
207
- const context = {
208
- files: files.value,
209
- options,
210
- emit: (event, payload) => {
211
- const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
212
- emitter.emit(prefixedEvent, payload);
213
- }
214
- };
226
+ const context = createPluginContext(storagePlugin.id, files.value, options, emitter);
215
227
  await storagePlugin.hooks.remove(file, context);
216
228
  } catch (error) {
217
229
  console.error(`Storage plugin remove error:`, error);
218
230
  }
219
231
  }
220
232
  }
233
+ cleanupObjectURLs(createdObjectURLs, file.id);
221
234
  files.value = files.value.filter((f) => f.id !== fileId);
222
235
  emitter.emit("file:removed", file);
223
236
  };
224
237
  const removeFiles = (fileIds) => {
225
238
  const removedFiles = files.value.filter((f) => fileIds.includes(f.id));
239
+ removedFiles.forEach((file) => {
240
+ cleanupObjectURLs(createdObjectURLs, file.id);
241
+ });
226
242
  files.value = files.value.filter((f) => !fileIds.includes(f.id));
227
243
  removedFiles.forEach((file) => {
228
244
  emitter.emit("file:removed", file);
@@ -231,6 +247,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
231
247
  };
232
248
  const clearFiles = () => {
233
249
  const allFiles = [...files.value];
250
+ cleanupObjectURLs(createdObjectURLs);
234
251
  files.value = [];
235
252
  allFiles.forEach((file) => {
236
253
  emitter.emit("file:removed", file);
@@ -239,9 +256,6 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
239
256
  };
240
257
  const getFileData = async (fileId) => {
241
258
  const file = getFile(fileId);
242
- if (!file) {
243
- throw new Error(`File not found: ${fileId}`);
244
- }
245
259
  if (file.size > 100 * 1024 * 1024) {
246
260
  console.warn(
247
261
  `getFileData: Loading large file (${(file.size / 1024 / 1024).toFixed(2)}MB) into memory. Consider using getFileURL() or getFileStream() for better performance.`
@@ -258,19 +272,19 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
258
272
  };
259
273
  const getFileURL = async (fileId) => {
260
274
  const file = getFile(fileId);
261
- if (!file) {
262
- throw new Error(`File not found: ${fileId}`);
263
- }
264
275
  if (file.source === "local") {
265
- return URL.createObjectURL(file.data);
276
+ const existingURL = createdObjectURLs.get(file.id);
277
+ if (existingURL) {
278
+ return existingURL;
279
+ }
280
+ const objectURL = URL.createObjectURL(file.data);
281
+ createdObjectURLs.set(file.id, objectURL);
282
+ return objectURL;
266
283
  }
267
284
  return file.remoteUrl;
268
285
  };
269
286
  const getFileStream = async (fileId) => {
270
287
  const file = getFile(fileId);
271
- if (!file) {
272
- throw new Error(`File not found: ${fileId}`);
273
- }
274
288
  if (file.source === "local") {
275
289
  return file.data.stream();
276
290
  }
@@ -280,11 +294,9 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
280
294
  }
281
295
  return response.body;
282
296
  };
283
- const replaceFileData = async (fileId, newData, newName) => {
297
+ const replaceFileData = async (fileId, newData, newName, shouldAutoUpload) => {
284
298
  const file = getFile(fileId);
285
- if (!file) {
286
- throw new Error(`File not found: ${fileId}`);
287
- }
299
+ cleanupObjectURLs(createdObjectURLs, fileId);
288
300
  const updatedFile = {
289
301
  ...file,
290
302
  source: "local",
@@ -294,12 +306,25 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
294
306
  status: "waiting",
295
307
  // Mark as needing upload
296
308
  progress: { percentage: 0 },
297
- remoteUrl: void 0
309
+ remoteUrl: void 0,
298
310
  // Clear old remote URL
311
+ meta: {}
312
+ // Clear old metadata (thumbnails, dimensions, etc.)
299
313
  };
314
+ const preprocessedFile = await runPluginStage("preprocess", updatedFile);
315
+ const finalFile = preprocessedFile || updatedFile;
300
316
  const index = files.value.findIndex((f) => f.id === fileId);
301
- files.value[index] = updatedFile;
302
- emitter.emit("file:added", updatedFile);
317
+ if (index === -1) {
318
+ throw new Error(`File not found: ${fileId}`);
319
+ }
320
+ files.value[index] = finalFile;
321
+ emitter.emit("file:replaced", finalFile);
322
+ emitter.emit("file:added", finalFile);
323
+ const shouldUpload = shouldAutoUpload ?? options.autoProceed;
324
+ if (shouldUpload) {
325
+ upload();
326
+ }
327
+ return finalFile;
303
328
  };
304
329
  const reorderFile = (oldIndex, newIndex) => {
305
330
  if (oldIndex === newIndex) {
@@ -321,20 +346,35 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
321
346
  emitter.emit("files:reorder", { oldIndex, newIndex });
322
347
  };
323
348
  const getFile = (fileId) => {
324
- return files.value.find((f) => f.id === fileId);
349
+ const file = files.value.find((f) => f.id === fileId);
350
+ if (!file) {
351
+ throw new Error(`File not found: ${fileId}`);
352
+ }
353
+ return file;
325
354
  };
326
355
  const upload = async () => {
327
356
  const filesToUpload = files.value.filter((f) => f.status === "waiting");
328
357
  emitter.emit("upload:start", filesToUpload);
329
358
  for (const file of filesToUpload) {
330
359
  try {
331
- updateFile(file.id, { status: "uploading" });
360
+ const processedFile = await runPluginStage("process", file);
361
+ if (!processedFile) {
362
+ const error = createFileError(file, new Error("File processing failed"));
363
+ updateFile(file.id, { status: "error", error });
364
+ emitter.emit("file:error", { file, error });
365
+ continue;
366
+ }
367
+ if (processedFile.id !== file.id) {
368
+ files.value = files.value.map((f) => f.id === file.id ? processedFile : f);
369
+ }
370
+ updateFile(processedFile.id, { status: "uploading" });
332
371
  const onProgress = (progress) => {
333
- updateFile(file.id, { progress: { percentage: progress } });
334
- emitter.emit("upload:progress", { file, progress });
372
+ updateFile(processedFile.id, { progress: { percentage: progress } });
373
+ emitter.emit("upload:progress", { file: processedFile, progress });
335
374
  };
336
375
  const storagePlugin = getStoragePlugin();
337
376
  let uploadResult;
377
+ let remoteUrl;
338
378
  if (storagePlugin?.hooks.upload) {
339
379
  const context = {
340
380
  files: files.value,
@@ -342,21 +382,18 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
342
382
  onProgress,
343
383
  emit: getPluginEmitFn(storagePlugin.id)
344
384
  };
345
- uploadResult = await storagePlugin.hooks.upload(file, context);
385
+ const result = await storagePlugin.hooks.upload(processedFile, context);
386
+ uploadResult = result;
387
+ remoteUrl = result.url;
346
388
  } else {
347
- uploadResult = await uploadFn(file, onProgress);
389
+ uploadResult = await uploadFn(processedFile, onProgress);
390
+ remoteUrl = typeof uploadResult === "string" ? uploadResult : void 0;
348
391
  }
349
- const remoteUrl = uploadResult?.url;
350
- updateFile(file.id, { status: "complete", uploadResult, remoteUrl });
392
+ const currentFile = files.value.find((f) => f.id === processedFile.id);
393
+ const preview = currentFile?.preview || remoteUrl;
394
+ updateFile(processedFile.id, { status: "complete", uploadResult, remoteUrl, preview });
351
395
  } catch (err) {
352
- const error = {
353
- message: err instanceof Error ? err.message : "Unknown upload error",
354
- details: {
355
- fileName: file.name,
356
- fileSize: file.size,
357
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
358
- }
359
- };
396
+ const error = createFileError(file, err);
360
397
  updateFile(file.id, { status: "error", error });
361
398
  emitter.emit("file:error", { file, error });
362
399
  }
@@ -365,13 +402,21 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
365
402
  emitter.emit("upload:complete", completed);
366
403
  };
367
404
  const reset = () => {
405
+ cleanupObjectURLs(createdObjectURLs);
368
406
  files.value = [];
369
407
  };
408
+ onBeforeUnmount(() => {
409
+ createdObjectURLs.forEach((url) => {
410
+ URL.revokeObjectURL(url);
411
+ });
412
+ createdObjectURLs.clear();
413
+ });
370
414
  const callPluginHook = async (hook, stage, file, context) => {
371
415
  switch (stage) {
372
416
  case "validate":
373
417
  await hook(file, context);
374
418
  return file;
419
+ case "preprocess":
375
420
  case "process":
376
421
  case "complete":
377
422
  if (!file) throw new Error("File is required for this hook type");
@@ -52,5 +52,5 @@ interface ImageCompressorOptions {
52
52
  */
53
53
  preserveMetadata?: boolean;
54
54
  }
55
- export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").Plugin<any, ImageCompressorEvents>;
55
+ export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").ProcessingPlugin<any, ImageCompressorEvents>;
56
56
  export {};
@@ -1,5 +1,5 @@
1
- import { defineUploaderPlugin } from "../types.js";
2
- export const PluginImageCompressor = defineUploaderPlugin((pluginOptions) => {
1
+ import { defineProcessingPlugin } from "../types.js";
2
+ export const PluginImageCompressor = defineProcessingPlugin((pluginOptions) => {
3
3
  const {
4
4
  maxWidth = 1920,
5
5
  maxHeight = 1920,
@@ -30,6 +30,17 @@ export interface AzureDataLakeOptions {
30
30
  * @default true
31
31
  */
32
32
  autoCreateDirectory?: boolean;
33
+ /**
34
+ * Number of retry attempts for failed operations
35
+ * @default 3
36
+ */
37
+ retries?: number;
38
+ /**
39
+ * Initial delay between retries in milliseconds
40
+ * Uses exponential backoff: delay * (2 ^ attempt)
41
+ * @default 1000 (1 second)
42
+ */
43
+ retryDelay?: number;
33
44
  }
34
45
  export interface AzureUploadResult {
35
46
  /**
@@ -41,4 +52,4 @@ export interface AzureUploadResult {
41
52
  */
42
53
  blobPath: string;
43
54
  }
44
- export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").Plugin<any, Record<string, never>>;
55
+ export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").StoragePlugin<AzureUploadResult, Record<string, never>>;
@@ -1,10 +1,34 @@
1
1
  import { ref } from "vue";
2
2
  import { DataLakeDirectoryClient } from "@azure/storage-file-datalake";
3
- import { defineUploaderPlugin } from "../../types.js";
4
- export const PluginAzureDataLake = defineUploaderPlugin((options) => {
3
+ import { defineStoragePlugin } from "../../types.js";
4
+ export const PluginAzureDataLake = defineStoragePlugin((options) => {
5
5
  const sasURL = ref(options.sasURL || "");
6
6
  let refreshPromise = null;
7
7
  const directoryCheckedCache = /* @__PURE__ */ new Set();
8
+ const maxRetries = options.retries ?? 3;
9
+ const initialRetryDelay = options.retryDelay ?? 1e3;
10
+ async function withRetry(operation, operationName) {
11
+ let lastError;
12
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
13
+ try {
14
+ return await operation();
15
+ } catch (error) {
16
+ lastError = error;
17
+ if (attempt === maxRetries) {
18
+ break;
19
+ }
20
+ const delay = initialRetryDelay * Math.pow(2, attempt);
21
+ if (import.meta.dev) {
22
+ console.warn(
23
+ `[Azure Storage] ${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}). Retrying in ${delay}ms...`,
24
+ error
25
+ );
26
+ }
27
+ await new Promise((resolve) => setTimeout(resolve, delay));
28
+ }
29
+ }
30
+ throw new Error(`[Azure Storage] ${operationName} failed after ${maxRetries + 1} attempts: ${lastError?.message}`);
31
+ }
8
32
  if (options.getSASUrl && !options.sasURL) {
9
33
  options.getSASUrl().then((url) => {
10
34
  sasURL.value = url;
@@ -61,46 +85,52 @@ export const PluginAzureDataLake = defineUploaderPlugin((options) => {
61
85
  if (file.source !== "local" || file.data === null) {
62
86
  throw new Error("Cannot upload remote file - no local data available");
63
87
  }
64
- const fileClient = await getFileClient(file.id);
65
- await fileClient.upload(file.data, {
66
- metadata: {
67
- ...options.metadata,
68
- mimeType: file.mimeType,
69
- size: String(file.size),
70
- originalName: file.name
71
- },
72
- pathHttpHeaders: {
73
- ...options.pathHttpHeaders,
74
- contentType: file.mimeType
75
- },
76
- onProgress: ({ loadedBytes }) => {
77
- const uploadedPercentage = Math.round(loadedBytes / file.size * 100);
78
- context.onProgress(uploadedPercentage);
79
- }
80
- });
81
- return {
82
- url: fileClient.url,
83
- blobPath: fileClient.name
84
- };
88
+ return withRetry(async () => {
89
+ const fileClient = await getFileClient(file.id);
90
+ await fileClient.upload(file.data, {
91
+ metadata: {
92
+ ...options.metadata,
93
+ mimeType: file.mimeType,
94
+ size: String(file.size),
95
+ originalName: file.name
96
+ },
97
+ pathHttpHeaders: {
98
+ ...options.pathHttpHeaders,
99
+ contentType: file.mimeType
100
+ },
101
+ onProgress: ({ loadedBytes }) => {
102
+ const uploadedPercentage = Math.round(loadedBytes / file.size * 100);
103
+ context.onProgress(uploadedPercentage);
104
+ }
105
+ });
106
+ return {
107
+ url: fileClient.url,
108
+ blobPath: fileClient.name
109
+ };
110
+ }, `Upload file "${file.name}"`);
85
111
  },
86
112
  /**
87
113
  * Get remote file metadata from Azure
88
114
  */
89
115
  async getRemoteFile(fileId, _context) {
90
- const fileClient = await getFileClient(fileId);
91
- const properties = await fileClient.getProperties();
92
- return {
93
- size: properties.contentLength || 0,
94
- mimeType: properties.contentType || "application/octet-stream",
95
- remoteUrl: fileClient.url
96
- };
116
+ return withRetry(async () => {
117
+ const fileClient = await getFileClient(fileId);
118
+ const properties = await fileClient.getProperties();
119
+ return {
120
+ size: properties.contentLength || 0,
121
+ mimeType: properties.contentType || "application/octet-stream",
122
+ remoteUrl: fileClient.url
123
+ };
124
+ }, `Get remote file "${fileId}"`);
97
125
  },
98
126
  /**
99
127
  * Delete file from Azure Blob Storage
100
128
  */
101
129
  async remove(file, _context) {
102
- const fileClient = await getFileClient(file.id);
103
- await fileClient.deleteIfExists();
130
+ return withRetry(async () => {
131
+ const fileClient = await getFileClient(file.id);
132
+ await fileClient.deleteIfExists();
133
+ }, `Delete file "${file.name}"`);
104
134
  }
105
135
  }
106
136
  };
@@ -1,7 +1,8 @@
1
1
  interface ThumbnailGeneratorOptions {
2
- width?: number;
3
- height?: number;
2
+ maxWidth?: number;
3
+ maxHeight?: number;
4
4
  quality?: number;
5
+ videoCaptureTime?: number;
5
6
  }
6
- export declare const PluginThumbnailGenerator: (options: ThumbnailGeneratorOptions) => import("../types.js").Plugin<any, Record<string, never>>;
7
+ export declare const PluginThumbnailGenerator: (options: ThumbnailGeneratorOptions) => import("../types.js").ProcessingPlugin<any, Record<string, never>>;
7
8
  export {};