nuxt-upload-kit 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -0
- package/dist/module.d.mts +14 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +29 -0
- package/dist/runtime/composables/useFFMpeg.d.ts +14 -0
- package/dist/runtime/composables/useFFMpeg.js +66 -0
- package/dist/runtime/composables/useUploadKit/index.d.ts +471 -0
- package/dist/runtime/composables/useUploadKit/index.js +486 -0
- package/dist/runtime/composables/useUploadKit/plugins/image-compressor.d.ts +56 -0
- package/dist/runtime/composables/useUploadKit/plugins/image-compressor.js +137 -0
- package/dist/runtime/composables/useUploadKit/plugins/index.d.ts +4 -0
- package/dist/runtime/composables/useUploadKit/plugins/index.js +4 -0
- package/dist/runtime/composables/useUploadKit/plugins/storage/azure-datalake.d.ts +55 -0
- package/dist/runtime/composables/useUploadKit/plugins/storage/azure-datalake.js +137 -0
- package/dist/runtime/composables/useUploadKit/plugins/storage/index.d.ts +10 -0
- package/dist/runtime/composables/useUploadKit/plugins/storage/index.js +1 -0
- package/dist/runtime/composables/useUploadKit/plugins/thumbnail-generator.d.ts +8 -0
- package/dist/runtime/composables/useUploadKit/plugins/thumbnail-generator.js +99 -0
- package/dist/runtime/composables/useUploadKit/plugins/video-compressor.d.ts +72 -0
- package/dist/runtime/composables/useUploadKit/plugins/video-compressor.js +111 -0
- package/dist/runtime/composables/useUploadKit/types.d.ts +488 -0
- package/dist/runtime/composables/useUploadKit/types.js +9 -0
- package/dist/runtime/composables/useUploadKit/utils.d.ts +23 -0
- package/dist/runtime/composables/useUploadKit/utils.js +45 -0
- package/dist/runtime/composables/useUploadKit/validators/allowed-file-types.d.ts +5 -0
- package/dist/runtime/composables/useUploadKit/validators/allowed-file-types.js +17 -0
- package/dist/runtime/composables/useUploadKit/validators/duplicate-file.d.ts +13 -0
- package/dist/runtime/composables/useUploadKit/validators/duplicate-file.js +27 -0
- package/dist/runtime/composables/useUploadKit/validators/index.d.ts +4 -0
- package/dist/runtime/composables/useUploadKit/validators/index.js +4 -0
- package/dist/runtime/composables/useUploadKit/validators/max-file-size.d.ts +5 -0
- package/dist/runtime/composables/useUploadKit/validators/max-file-size.js +17 -0
- package/dist/runtime/composables/useUploadKit/validators/max-files.d.ts +5 -0
- package/dist/runtime/composables/useUploadKit/validators/max-files.js +17 -0
- package/dist/runtime/types/index.d.ts +3 -0
- package/dist/runtime/types/index.js +3 -0
- package/dist/types.d.mts +5 -0
- package/package.json +84 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import type { Emitter } from "mitt";
|
|
2
|
+
/**
|
|
3
|
+
* PUBLIC API - Types users commonly need
|
|
4
|
+
* These are exported from the main package
|
|
5
|
+
*/
|
|
6
|
+
export type FileStatus = "waiting" | "preprocessing" | "uploading" | "postprocessing" | "complete" | "error";
|
|
7
|
+
export type UploadStatus = "waiting" | "uploading";
|
|
8
|
+
export interface FileProgress {
|
|
9
|
+
percentage: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FileError {
|
|
12
|
+
message: string;
|
|
13
|
+
details?: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* File source - indicates where the file originated from
|
|
17
|
+
*
|
|
18
|
+
* - 'local': File selected from user's device
|
|
19
|
+
* - 'storage': File loaded from remote storage (was previously uploaded)
|
|
20
|
+
* - Cloud picker sources: Files picked from cloud providers (future)
|
|
21
|
+
* - 'instagram': Instagram picker
|
|
22
|
+
* - 'dropbox': Dropbox picker
|
|
23
|
+
* - 'google-drive': Google Drive picker
|
|
24
|
+
* - 'onedrive': OneDrive picker
|
|
25
|
+
* - ... add more as needed
|
|
26
|
+
*/
|
|
27
|
+
export type FileSource = "local" | "storage" | "instagram" | "dropbox" | "google-drive" | "onedrive";
|
|
28
|
+
/**
|
|
29
|
+
* Base properties shared by both local and remote upload files
|
|
30
|
+
*/
|
|
31
|
+
export interface BaseUploadFile<TUploadResult = any> {
|
|
32
|
+
/** Unique identifier for the file */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Original filename (e.g., "vacation-photo.jpg") */
|
|
35
|
+
name: string;
|
|
36
|
+
/** File size in bytes */
|
|
37
|
+
size: number;
|
|
38
|
+
/** MIME type (e.g., "image/jpeg", "video/mp4") */
|
|
39
|
+
mimeType: string;
|
|
40
|
+
/**
|
|
41
|
+
* Current upload status
|
|
42
|
+
*/
|
|
43
|
+
status: FileStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Preview URL for displaying in UI (always populated after initialization)
|
|
46
|
+
* - For local files: Generated by thumbnail plugin as data URL, or remoteUrl after upload
|
|
47
|
+
* - For remote files: Provided by backend/storage, or falls back to remoteUrl
|
|
48
|
+
* - No manual fallback needed: Always available for display
|
|
49
|
+
*/
|
|
50
|
+
preview?: string;
|
|
51
|
+
/** Upload progress tracking */
|
|
52
|
+
progress: FileProgress;
|
|
53
|
+
/** Error information if upload fails */
|
|
54
|
+
error?: FileError;
|
|
55
|
+
/**
|
|
56
|
+
* Result returned by the upload function/storage plugin
|
|
57
|
+
* Can contain additional metadata like etag, key, etc.
|
|
58
|
+
*/
|
|
59
|
+
uploadResult?: TUploadResult;
|
|
60
|
+
/**
|
|
61
|
+
* Custom metadata for storing additional file information
|
|
62
|
+
* Plugins can add data here (e.g., { extension: 'jpg', originalWidth: 4000 })
|
|
63
|
+
*/
|
|
64
|
+
meta: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Local upload file - originates from user's device
|
|
68
|
+
*
|
|
69
|
+
* Lifecycle:
|
|
70
|
+
* 1. User selects file → created with data, no remoteUrl
|
|
71
|
+
* 2. Validation → runs validate hooks
|
|
72
|
+
* 3. Preprocessing → generates thumbnails (preview set here)
|
|
73
|
+
* 4. User clicks upload → runs process hooks (compression)
|
|
74
|
+
* 5. Upload → remoteUrl set after successful upload
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const localFile: LocalUploadFile = {
|
|
79
|
+
* source: 'local',
|
|
80
|
+
* data: fileBlob, // Has local data
|
|
81
|
+
* remoteUrl: undefined, // Not uploaded yet
|
|
82
|
+
* preview: 'data:image/jpeg;base64,...', // Generated by thumbnail plugin
|
|
83
|
+
* ...
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export interface LocalUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
88
|
+
/** Always 'local' for files selected from user's device */
|
|
89
|
+
source: "local";
|
|
90
|
+
/**
|
|
91
|
+
* The actual file data (File from input or Blob)
|
|
92
|
+
* Available for processing, compression, thumbnail generation, etc.
|
|
93
|
+
*/
|
|
94
|
+
data: File | Blob;
|
|
95
|
+
/**
|
|
96
|
+
* URL where file was uploaded (set after successful upload)
|
|
97
|
+
* undefined before upload, populated after upload completes
|
|
98
|
+
* Use to check if file has been uploaded: `if (file.remoteUrl) { ... }`
|
|
99
|
+
*/
|
|
100
|
+
remoteUrl?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Remote upload file - originates from remote source
|
|
104
|
+
*
|
|
105
|
+
* These files already exist remotely and don't need uploading.
|
|
106
|
+
* Common sources:
|
|
107
|
+
* - 'storage': Previously uploaded files being re-loaded
|
|
108
|
+
* - 'instagram', 'dropbox', etc.: Files from cloud pickers
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const remoteFile: RemoteUploadFile = {
|
|
113
|
+
* source: 'storage',
|
|
114
|
+
* data: null, // No local data - file is remote
|
|
115
|
+
* remoteUrl: 'https://storage.com/file.jpg', // Always present
|
|
116
|
+
* preview: 'https://storage.com/thumbnails/file.jpg', // Optional, from backend
|
|
117
|
+
* status: 'complete', // Already uploaded
|
|
118
|
+
* ...
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export interface RemoteUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
123
|
+
/**
|
|
124
|
+
* Source of the remote file
|
|
125
|
+
* - 'storage': File from your storage (previously uploaded)
|
|
126
|
+
* - 'instagram', 'dropbox', etc.: File from cloud picker
|
|
127
|
+
*/
|
|
128
|
+
source: Exclude<FileSource, "local">;
|
|
129
|
+
/**
|
|
130
|
+
* Always null for remote files (no local data available)
|
|
131
|
+
* File exists remotely and is accessed via remoteUrl
|
|
132
|
+
*/
|
|
133
|
+
data: null;
|
|
134
|
+
/**
|
|
135
|
+
* URL to the remote file (always present for remote files)
|
|
136
|
+
* Use this to display/download the file
|
|
137
|
+
*/
|
|
138
|
+
remoteUrl: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Upload file discriminated union
|
|
142
|
+
* Use file.source to narrow the type in your code:
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* if (file.source === 'local') {
|
|
147
|
+
* // TypeScript knows: file is LocalUploadFile
|
|
148
|
+
* URL.createObjectURL(file.data)
|
|
149
|
+
* } else {
|
|
150
|
+
* // TypeScript knows: file is RemoteUploadFile
|
|
151
|
+
* console.log(file.remoteUrl)
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export type UploadFile<TUploadResult = any> = LocalUploadFile<TUploadResult> | RemoteUploadFile<TUploadResult>;
|
|
156
|
+
export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
|
|
157
|
+
export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
|
|
158
|
+
export interface UploadOptions {
|
|
159
|
+
/**
|
|
160
|
+
* Storage plugin for uploading files (only one storage plugin can be active)
|
|
161
|
+
*
|
|
162
|
+
* Storage plugins handle the actual upload, download, and deletion of files
|
|
163
|
+
* from remote storage (Azure, S3, GCS, etc.)
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* storage: PluginAzureDataLake({
|
|
168
|
+
* sasURL: 'https://...',
|
|
169
|
+
* path: 'uploads'
|
|
170
|
+
* })
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
storage?: StoragePlugin<any, any>;
|
|
174
|
+
/**
|
|
175
|
+
* Processing and validation plugins (validators, compressors, etc.)
|
|
176
|
+
*
|
|
177
|
+
* These plugins run during the file lifecycle:
|
|
178
|
+
* - validate: Check file before adding
|
|
179
|
+
* - preprocess: Generate thumbnails/previews immediately
|
|
180
|
+
* - process: Compress/transform before upload
|
|
181
|
+
* - complete: Post-upload processing
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* plugins: [
|
|
186
|
+
* ValidatorMaxFiles({ maxFiles: 10 }),
|
|
187
|
+
* PluginImageCompressor({ quality: 0.8 })
|
|
188
|
+
* ]
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
plugins?: ProcessingPlugin<any, any>[];
|
|
192
|
+
/**
|
|
193
|
+
* Validate maximum number of files
|
|
194
|
+
* - false: disabled
|
|
195
|
+
* - number: enabled with limit
|
|
196
|
+
* @default false
|
|
197
|
+
*/
|
|
198
|
+
maxFiles?: false | number;
|
|
199
|
+
/**
|
|
200
|
+
* Validate maximum file size in bytes
|
|
201
|
+
* - false: disabled
|
|
202
|
+
* - number: enabled with limit
|
|
203
|
+
* @default false
|
|
204
|
+
*/
|
|
205
|
+
maxFileSize?: false | number;
|
|
206
|
+
/**
|
|
207
|
+
* Validate allowed file MIME types
|
|
208
|
+
* - false: disabled
|
|
209
|
+
* - string[]: enabled with allowed types
|
|
210
|
+
* @default false
|
|
211
|
+
*/
|
|
212
|
+
allowedFileTypes?: false | string[];
|
|
213
|
+
/**
|
|
214
|
+
* Generate thumbnail previews for images/videos
|
|
215
|
+
* - false: disabled
|
|
216
|
+
* - true: enabled with defaults
|
|
217
|
+
* - object: enabled with custom options
|
|
218
|
+
* @default false
|
|
219
|
+
*/
|
|
220
|
+
thumbnails?: false | true | ThumbnailOptions;
|
|
221
|
+
/**
|
|
222
|
+
* Compress images before upload
|
|
223
|
+
* - false: disabled
|
|
224
|
+
* - true: enabled with defaults
|
|
225
|
+
* - object: enabled with custom options
|
|
226
|
+
* @default false
|
|
227
|
+
*/
|
|
228
|
+
imageCompression?: false | true | ImageCompressionOptions;
|
|
229
|
+
/**
|
|
230
|
+
* Automatically start upload after files are added
|
|
231
|
+
* @default false
|
|
232
|
+
*/
|
|
233
|
+
autoProceed?: boolean;
|
|
234
|
+
}
|
|
235
|
+
export interface ThumbnailOptions {
|
|
236
|
+
width?: number;
|
|
237
|
+
height?: number;
|
|
238
|
+
quality?: number;
|
|
239
|
+
}
|
|
240
|
+
export interface ImageCompressionOptions {
|
|
241
|
+
maxWidth?: number;
|
|
242
|
+
maxHeight?: number;
|
|
243
|
+
quality?: number;
|
|
244
|
+
outputFormat?: "jpeg" | "webp" | "png" | "auto";
|
|
245
|
+
minSizeToCompress?: number;
|
|
246
|
+
preserveMetadata?: boolean;
|
|
247
|
+
}
|
|
248
|
+
type CoreUploaderEvents<TUploadResult = any> = {
|
|
249
|
+
"file:added": Readonly<UploadFile<TUploadResult>>;
|
|
250
|
+
"file:removed": Readonly<UploadFile<TUploadResult>>;
|
|
251
|
+
"file:replaced": Readonly<UploadFile<TUploadResult>>;
|
|
252
|
+
"file:processing": Readonly<UploadFile<TUploadResult>>;
|
|
253
|
+
"file:error": {
|
|
254
|
+
file: Readonly<UploadFile<TUploadResult>>;
|
|
255
|
+
error: FileError;
|
|
256
|
+
};
|
|
257
|
+
"upload:start": Array<Readonly<UploadFile<TUploadResult>>>;
|
|
258
|
+
"upload:complete": Array<Required<Readonly<UploadFile<TUploadResult>>>>;
|
|
259
|
+
"upload:error": FileError;
|
|
260
|
+
"upload:progress": {
|
|
261
|
+
file: Readonly<UploadFile<TUploadResult>>;
|
|
262
|
+
progress: number;
|
|
263
|
+
};
|
|
264
|
+
"files:reorder": {
|
|
265
|
+
oldIndex: number;
|
|
266
|
+
newIndex: number;
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
export type UploaderEvents<TUploadResult = any> = CoreUploaderEvents<TUploadResult>;
|
|
270
|
+
/**
|
|
271
|
+
* PLUGIN API - Types for building custom plugins
|
|
272
|
+
* Only needed if users want to create custom validators/processors
|
|
273
|
+
*/
|
|
274
|
+
export type PluginContext<TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
275
|
+
files: UploadFile[];
|
|
276
|
+
options: UploadOptions;
|
|
277
|
+
/**
|
|
278
|
+
* Emit custom plugin events
|
|
279
|
+
* Events are automatically prefixed with the plugin ID
|
|
280
|
+
*/
|
|
281
|
+
emit: <K extends keyof TPluginEvents>(event: K, payload: TPluginEvents[K]) => void;
|
|
282
|
+
};
|
|
283
|
+
export type ValidationHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<true | UploadFile>;
|
|
284
|
+
export type ProcessingHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<UploadFile>;
|
|
285
|
+
export type SetupHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* Storage hooks for handling upload/download/deletion operations
|
|
288
|
+
*
|
|
289
|
+
* Storage plugins MUST return an object containing a `url` property.
|
|
290
|
+
* This URL will be set as the file's `remoteUrl` after successful upload.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* upload: async (file, context) => {
|
|
295
|
+
* // Upload logic...
|
|
296
|
+
* return {
|
|
297
|
+
* url: 'https://storage.example.com/file.jpg', // Required
|
|
298
|
+
* key: 'uploads/file.jpg', // Optional
|
|
299
|
+
* etag: 'abc123' // Optional
|
|
300
|
+
* }
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
export type UploadHook<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile<TUploadResult>, context: PluginContext<TPluginEvents> & {
|
|
305
|
+
onProgress: (progress: number) => void;
|
|
306
|
+
}) => Promise<TUploadResult & {
|
|
307
|
+
url: string;
|
|
308
|
+
}>;
|
|
309
|
+
export type GetRemoteFileHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (fileId: string, context: PluginContext<TPluginEvents>) => Promise<MinimumRemoteFileAttributes>;
|
|
310
|
+
export type RemoveHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
311
|
+
export type PluginLifecycleStage = "validate" | "preprocess" | "process" | "upload" | "complete";
|
|
312
|
+
/**
|
|
313
|
+
* Processing plugin hooks (validators, compressors, thumbnail generators)
|
|
314
|
+
*/
|
|
315
|
+
export type ProcessingPluginHooks<TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
316
|
+
validate?: ValidationHook<TPluginEvents>;
|
|
317
|
+
preprocess?: ProcessingHook<TPluginEvents>;
|
|
318
|
+
process?: ProcessingHook<TPluginEvents>;
|
|
319
|
+
complete?: ProcessingHook<TPluginEvents>;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Storage plugin hooks (upload, download, delete from remote storage)
|
|
323
|
+
*/
|
|
324
|
+
export type StoragePluginHooks<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
325
|
+
upload: UploadHook<TUploadResult, TPluginEvents>;
|
|
326
|
+
getRemoteFile?: GetRemoteFileHook<TPluginEvents>;
|
|
327
|
+
remove?: RemoveHook<TPluginEvents>;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* All possible plugin hooks (for internal use)
|
|
331
|
+
*/
|
|
332
|
+
export type PluginHooks<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
333
|
+
validate?: ValidationHook<TPluginEvents>;
|
|
334
|
+
preprocess?: ProcessingHook<TPluginEvents>;
|
|
335
|
+
process?: ProcessingHook<TPluginEvents>;
|
|
336
|
+
upload?: UploadHook<TUploadResult, TPluginEvents>;
|
|
337
|
+
getRemoteFile?: GetRemoteFileHook<TPluginEvents>;
|
|
338
|
+
remove?: RemoveHook<TPluginEvents>;
|
|
339
|
+
complete?: ProcessingHook<TPluginEvents>;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Processing plugin (validators, compressors, thumbnail generators)
|
|
343
|
+
*
|
|
344
|
+
* These plugins transform or validate files without handling storage.
|
|
345
|
+
*/
|
|
346
|
+
export interface ProcessingPlugin<_TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> {
|
|
347
|
+
id: string;
|
|
348
|
+
hooks: ProcessingPluginHooks<TPluginEvents>;
|
|
349
|
+
options?: UploadOptions;
|
|
350
|
+
events?: TPluginEvents;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Storage plugin (Azure, S3, GCS, etc.)
|
|
354
|
+
*
|
|
355
|
+
* Storage plugins handle uploading, downloading, and deleting files from remote storage.
|
|
356
|
+
* Only one storage plugin can be active at a time.
|
|
357
|
+
*/
|
|
358
|
+
export interface StoragePlugin<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> {
|
|
359
|
+
id: string;
|
|
360
|
+
hooks: StoragePluginHooks<TUploadResult, TPluginEvents>;
|
|
361
|
+
options?: UploadOptions;
|
|
362
|
+
events?: TPluginEvents;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Base plugin interface (for internal use - supports both types)
|
|
366
|
+
*/
|
|
367
|
+
export interface Plugin<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> {
|
|
368
|
+
id: string;
|
|
369
|
+
hooks: PluginHooks<TUploadResult, TPluginEvents>;
|
|
370
|
+
options?: UploadOptions;
|
|
371
|
+
events?: TPluginEvents;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Define a processing plugin (validators, compressors, thumbnail generators)
|
|
375
|
+
*
|
|
376
|
+
* @example Validator
|
|
377
|
+
* ```typescript
|
|
378
|
+
* export const ValidatorMaxFiles = defineProcessingPlugin<ValidatorOptions>((options) => ({
|
|
379
|
+
* id: 'validator-max-files',
|
|
380
|
+
* hooks: {
|
|
381
|
+
* validate: async (file, context) => {
|
|
382
|
+
* if (context.files.length >= options.maxFiles) {
|
|
383
|
+
* throw { message: 'Too many files' }
|
|
384
|
+
* }
|
|
385
|
+
* return file
|
|
386
|
+
* }
|
|
387
|
+
* }
|
|
388
|
+
* }))
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
export declare function defineProcessingPlugin<TPluginOptions = unknown, TPluginEvents extends Record<string, any> = Record<string, never>>(factory: (options: TPluginOptions) => ProcessingPlugin<any, TPluginEvents>): (options: TPluginOptions) => ProcessingPlugin<any, TPluginEvents>;
|
|
392
|
+
/**
|
|
393
|
+
* Define a storage plugin (Azure, S3, GCS, etc.)
|
|
394
|
+
*
|
|
395
|
+
* Storage plugins MUST implement the `upload` hook and should return an object with a `url` property.
|
|
396
|
+
*
|
|
397
|
+
* @example Azure Storage
|
|
398
|
+
* ```typescript
|
|
399
|
+
* export const PluginAzureDataLake = defineStoragePlugin<AzureOptions, AzureEvents>((options) => ({
|
|
400
|
+
* id: 'azure-datalake-storage',
|
|
401
|
+
* hooks: {
|
|
402
|
+
* upload: async (file, context) => {
|
|
403
|
+
* const fileClient = await getFileClient(file.id)
|
|
404
|
+
* await fileClient.upload(file.data, { onProgress: context.onProgress })
|
|
405
|
+
* return { url: fileClient.url, blobPath: fileClient.name }
|
|
406
|
+
* },
|
|
407
|
+
* getRemoteFile: async (fileId, context) => {
|
|
408
|
+
* // ... fetch file metadata ...
|
|
409
|
+
* },
|
|
410
|
+
* remove: async (file, context) => {
|
|
411
|
+
* // ... delete file ...
|
|
412
|
+
* }
|
|
413
|
+
* }
|
|
414
|
+
* }))
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
export declare function defineStoragePlugin<TPluginOptions = unknown, TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>>(factory: (options: TPluginOptions) => StoragePlugin<TUploadResult, TPluginEvents>): (options: TPluginOptions) => StoragePlugin<TUploadResult, TPluginEvents>;
|
|
418
|
+
/**
|
|
419
|
+
* Define an uploader plugin with type safety, context access, and custom events.
|
|
420
|
+
* This is the universal plugin factory for all plugin types (storage, validators, processors).
|
|
421
|
+
*
|
|
422
|
+
* @deprecated Use defineProcessingPlugin or defineStoragePlugin instead for better type safety
|
|
423
|
+
*
|
|
424
|
+
* Hooks receive context as a parameter, making it clear when context is available.
|
|
425
|
+
* Context includes current files, options, and an emit function for custom events.
|
|
426
|
+
*
|
|
427
|
+
* @example Basic Plugin
|
|
428
|
+
* ```typescript
|
|
429
|
+
* export const ValidatorMaxFiles = defineUploaderPlugin<ValidatorOptions>((options) => ({
|
|
430
|
+
* id: 'validator-max-files',
|
|
431
|
+
* hooks: {
|
|
432
|
+
* validate: async (file, context) => {
|
|
433
|
+
* if (context.files.length >= options.maxFiles) {
|
|
434
|
+
* throw { message: 'Too many files' }
|
|
435
|
+
* }
|
|
436
|
+
* return file
|
|
437
|
+
* }
|
|
438
|
+
* }
|
|
439
|
+
* }))
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @example Plugin with Custom Events
|
|
443
|
+
* ```typescript
|
|
444
|
+
* type CompressionEvents = {
|
|
445
|
+
* start: { file: UploadFile; originalSize: number }
|
|
446
|
+
* complete: { file: UploadFile; savedBytes: number }
|
|
447
|
+
* }
|
|
448
|
+
*
|
|
449
|
+
* export const PluginImageCompressor = defineUploaderPlugin<
|
|
450
|
+
* ImageCompressorOptions,
|
|
451
|
+
* CompressionEvents
|
|
452
|
+
* >((options) => ({
|
|
453
|
+
* id: 'image-compressor',
|
|
454
|
+
* hooks: {
|
|
455
|
+
* process: async (file, context) => {
|
|
456
|
+
* context.emit('start', { file, originalSize: file.size })
|
|
457
|
+
* // ... compression logic ...
|
|
458
|
+
* context.emit('complete', { file, savedBytes: 1000 })
|
|
459
|
+
* return file
|
|
460
|
+
* }
|
|
461
|
+
* }
|
|
462
|
+
* }))
|
|
463
|
+
*
|
|
464
|
+
* // Usage - events are automatically prefixed with plugin ID
|
|
465
|
+
* uploader.on('image-compressor:complete', ({ file, savedBytes }) => {
|
|
466
|
+
* console.log(`Saved ${savedBytes} bytes`)
|
|
467
|
+
* })
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
export declare function defineUploaderPlugin<TPluginOptions = unknown, TPluginEvents extends Record<string, any> = Record<string, never>>(factory: (options: TPluginOptions) => Plugin<any, TPluginEvents>): (options: TPluginOptions) => Plugin<any, TPluginEvents>;
|
|
471
|
+
/**
|
|
472
|
+
* INTERNAL TYPES - Not commonly needed by users
|
|
473
|
+
* Kept exported for edge cases but not primary API
|
|
474
|
+
*/
|
|
475
|
+
export type PreProcessor = (file: UploadFile) => Promise<UploadFile>;
|
|
476
|
+
export type Uploader = (file: Readonly<UploadFile>, emiter: Emitter<Pick<UploaderEvents, "upload:error" | "upload:progress">>) => Promise<string>;
|
|
477
|
+
export type Validator = (file: UploadFile) => Promise<boolean | FileError>;
|
|
478
|
+
export type Processor = (file: UploadFile) => Promise<File | Blob>;
|
|
479
|
+
export interface UploadBlob {
|
|
480
|
+
blobPath: string;
|
|
481
|
+
}
|
|
482
|
+
type MinimumRemoteFileAttributes = {
|
|
483
|
+
size: number;
|
|
484
|
+
mimeType: string;
|
|
485
|
+
remoteUrl: string;
|
|
486
|
+
preview?: string;
|
|
487
|
+
};
|
|
488
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PluginContext, UploadFile, FileError, UploadOptions } from "./types.js";
|
|
2
|
+
import type { Emitter } from "mitt";
|
|
3
|
+
/**
|
|
4
|
+
* Create a plugin context object with consistent structure
|
|
5
|
+
*/
|
|
6
|
+
export declare function createPluginContext<TPluginEvents extends Record<string, any> = Record<string, never>>(pluginId: string, files: UploadFile[], options: UploadOptions, emitter: Emitter<any>): PluginContext<TPluginEvents>;
|
|
7
|
+
/**
|
|
8
|
+
* Create a consistent file error object
|
|
9
|
+
*/
|
|
10
|
+
export declare function createFileError(file: UploadFile, error: unknown): FileError;
|
|
11
|
+
/**
|
|
12
|
+
* Calculate thumbnail dimensions while maintaining aspect ratio
|
|
13
|
+
*/
|
|
14
|
+
export declare function calculateThumbnailDimensions(originalWidth: number, originalHeight: number, maxWidth: number, maxHeight: number): {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Cleanup object URLs to prevent memory leaks
|
|
20
|
+
* @param urlMap Map of file IDs to object URLs
|
|
21
|
+
* @param fileId Optional file ID to cleanup specific URL, or cleanup all if not provided
|
|
22
|
+
*/
|
|
23
|
+
export declare function cleanupObjectURLs(urlMap: Map<string, string>, fileId?: string): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function createPluginContext(pluginId, files, options, emitter) {
|
|
2
|
+
return {
|
|
3
|
+
files,
|
|
4
|
+
options,
|
|
5
|
+
emit: (event, payload) => {
|
|
6
|
+
const prefixedEvent = `${pluginId}:${String(event)}`;
|
|
7
|
+
emitter.emit(prefixedEvent, payload);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function createFileError(file, error) {
|
|
12
|
+
return {
|
|
13
|
+
message: error instanceof Error ? error.message : String(error),
|
|
14
|
+
details: {
|
|
15
|
+
fileName: file.name,
|
|
16
|
+
fileSize: file.size,
|
|
17
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function calculateThumbnailDimensions(originalWidth, originalHeight, maxWidth, maxHeight) {
|
|
22
|
+
const aspectRatio = originalWidth / originalHeight;
|
|
23
|
+
let width = maxWidth;
|
|
24
|
+
let height = maxHeight;
|
|
25
|
+
if (aspectRatio > 1) {
|
|
26
|
+
height = maxWidth / aspectRatio;
|
|
27
|
+
} else {
|
|
28
|
+
width = maxHeight * aspectRatio;
|
|
29
|
+
}
|
|
30
|
+
return { width, height };
|
|
31
|
+
}
|
|
32
|
+
export function cleanupObjectURLs(urlMap, fileId) {
|
|
33
|
+
if (fileId) {
|
|
34
|
+
const url = urlMap.get(fileId);
|
|
35
|
+
if (url) {
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
urlMap.delete(fileId);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
for (const url of urlMap.values()) {
|
|
41
|
+
URL.revokeObjectURL(url);
|
|
42
|
+
}
|
|
43
|
+
urlMap.clear();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineProcessingPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorAllowedFileTypes = defineProcessingPlugin((options) => {
|
|
3
|
+
return {
|
|
4
|
+
id: "validator-allowed-file-types",
|
|
5
|
+
hooks: {
|
|
6
|
+
validate: async (file, _context) => {
|
|
7
|
+
if (!options.allowedFileTypes || options.allowedFileTypes.length === 0) {
|
|
8
|
+
return file;
|
|
9
|
+
}
|
|
10
|
+
if (options.allowedFileTypes.includes(file.mimeType)) {
|
|
11
|
+
return file;
|
|
12
|
+
}
|
|
13
|
+
throw { message: `File type ${file.mimeType} is not allowed` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface ValidatorDuplicateFileOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Whether to allow duplicate files
|
|
4
|
+
* @default false
|
|
5
|
+
*/
|
|
6
|
+
allowDuplicates?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Custom error message for duplicates
|
|
9
|
+
*/
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const ValidatorDuplicateFile: (options: ValidatorDuplicateFileOptions) => import("../types.js").Plugin<any, Record<string, never>>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorDuplicateFile = defineUploaderPlugin((options) => {
|
|
3
|
+
const { allowDuplicates = false, errorMessage = "This file has already been added" } = options;
|
|
4
|
+
return {
|
|
5
|
+
id: "validator-duplicate-file",
|
|
6
|
+
hooks: {
|
|
7
|
+
validate: async (file, context) => {
|
|
8
|
+
if (allowDuplicates) {
|
|
9
|
+
return file;
|
|
10
|
+
}
|
|
11
|
+
const isDuplicate = context.files.some((existingFile) => {
|
|
12
|
+
const sameSize = existingFile.size === file.size;
|
|
13
|
+
const sameName = existingFile.name === file.name;
|
|
14
|
+
let sameDate = true;
|
|
15
|
+
if (file.data instanceof File && existingFile.data instanceof File) {
|
|
16
|
+
sameDate = existingFile.data.lastModified === file.data.lastModified;
|
|
17
|
+
}
|
|
18
|
+
return sameSize && sameName && sameDate;
|
|
19
|
+
});
|
|
20
|
+
if (isDuplicate) {
|
|
21
|
+
throw { message: errorMessage, details: { fileName: file.name } };
|
|
22
|
+
}
|
|
23
|
+
return file;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineProcessingPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorMaxFileSize = defineProcessingPlugin((options) => {
|
|
3
|
+
return {
|
|
4
|
+
id: "validator-max-file-size",
|
|
5
|
+
hooks: {
|
|
6
|
+
validate: async (file, _context) => {
|
|
7
|
+
if (!options.maxFileSize || options.maxFileSize === Infinity) {
|
|
8
|
+
return file;
|
|
9
|
+
}
|
|
10
|
+
if (file.size <= options.maxFileSize) {
|
|
11
|
+
return file;
|
|
12
|
+
}
|
|
13
|
+
throw { message: `File size exceeds the maximum limit of ${options.maxFileSize} bytes` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
});
|